1 /* 2 * This file is part of libbluray 3 * Copyright (C) 2010 William Hahne 4 * Copyright (C) 2012-2019 Petri Hintukainen <phintuka@users.sourceforge.net> 5 * 6 * This library is free software; you can redistribute it and/or 7 * modify it under the terms of the GNU Lesser General Public 8 * License as published by the Free Software Foundation; either 9 * version 2.1 of the License, or (at your option) any later version. 10 * 11 * This library is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 * Lesser General Public License for more details. 15 * 16 * You should have received a copy of the GNU Lesser General Public 17 * License along with this library. If not, see 18 * <http://www.gnu.org/licenses/>. 19 */ 20 21 package org.videolan.media.content.playlist; 22 23 import java.io.IOException; 24 25 import javax.media.ClockStartedError; 26 import javax.media.ConnectionErrorEvent; 27 import javax.media.Control; 28 import javax.media.ControllerErrorEvent; 29 import javax.media.IncompatibleSourceException; 30 import javax.media.Time; 31 import javax.media.protocol.DataSource; 32 import javax.tv.locator.Locator; 33 import javax.tv.locator.InvalidLocatorException; 34 import javax.tv.service.selection.ServiceContextFactory; 35 36 import org.bluray.media.InvalidPlayListException; 37 import org.bluray.net.BDLocator; 38 import org.bluray.system.RegisterAccess; 39 import org.bluray.ti.selection.TitleContextImpl; 40 41 import org.videolan.BDJAction; 42 import org.videolan.Libbluray; 43 import org.videolan.Logger; 44 import org.videolan.PlaylistInfo; 45 import org.videolan.TIClip; 46 import org.videolan.media.content.BDHandler; 47 48 import org.videolan.media.content.control.MediaTimePositionControlImpl; 49 import org.videolan.media.content.control.OverallGainControlImpl; 50 import org.videolan.media.content.control.PanningControlImpl; 51 import org.videolan.media.content.control.PrimaryGainControlImpl; 52 import org.videolan.media.content.control.SecondaryGainControlImpl; 53 54 public class Handler extends BDHandler { Handler()55 public Handler() { 56 controls = new Control[18]; 57 controls[0] = new AngleControlImpl(this); 58 controls[1] = new AudioMetadataControlImpl(); 59 controls[2] = new BackgroundVideoPresentationControlImpl(this); 60 controls[3] = new DVBMediaSelectControlImpl(this); 61 controls[4] = new MediaTimeEventControlImpl(); 62 controls[5] = new MediaTimePositionControlImpl(this); 63 controls[6] = new OverallGainControlImpl(this); 64 controls[7] = new PanningControlImpl(this); 65 controls[8] = new PiPControlImpl(this); 66 controls[9] = new PlaybackControlImpl(this); 67 controls[10] = new PlayListChangeControlImpl(this); 68 controls[11] = new PrimaryAudioControlImpl(this); 69 controls[12] = new PrimaryGainControlImpl(this); 70 controls[13] = new SecondaryAudioControlImpl(this); 71 controls[14] = new SecondaryGainControlImpl(this); 72 controls[15] = new SubtitlingControlImpl(this); 73 controls[16] = new UOMaskTableControlImpl(this); 74 controls[17] = new VideoFormatControlImpl(this); 75 } 76 setSource(DataSource source)77 public void setSource(DataSource source) throws IOException, IncompatibleSourceException { 78 79 /* validate source */ 80 BDLocator newLocator; 81 try { 82 newLocator = new BDLocator(source.getLocator().toExternalForm()); 83 } catch (org.davic.net.InvalidLocatorException e) { 84 logger.error("incompatible source: " + source); 85 throw new IncompatibleSourceException(); 86 } catch (Exception e) { 87 logger.error("unexpected expection: " + e); 88 throw new IncompatibleSourceException(); 89 } 90 91 if (!newLocator.isPlayListItem()) { 92 logger.error("not playlist: " + newLocator); 93 throw new IncompatibleSourceException(); 94 } 95 96 /* get playlist info */ 97 PlaylistInfo newPi = Libbluray.getPlaylistInfo(newLocator.getPlayListId()); 98 if (newPi == null) { 99 logger.error("getPlaylistInfo failed for " + newLocator); 100 throw new IOException(); 101 } 102 103 /* commit changes and prefetch */ 104 synchronized (this) { 105 106 sourceLocator = newLocator; 107 currentLocator = null; 108 pi = newPi; 109 110 baseMediaTime = 0; 111 if (state == Prefetched) 112 doPrefetch(); 113 } 114 } 115 getServiceContentLocators()116 public Locator[] getServiceContentLocators() { 117 if (sourceLocator == null) 118 return new Locator[0]; 119 Locator[] locators = new Locator[1]; 120 if (currentLocator != null && getState() >= Prefetched) 121 locators[0] = currentLocator; 122 else 123 locators[0] = sourceLocator; 124 return locators; 125 } 126 getDuration()127 public Time getDuration() { 128 if (pi != null) { 129 long duration = pi.getDuration(); 130 return new Time(duration * TO_SECONDS); 131 } 132 return DURATION_UNKNOWN; 133 } 134 doPrefetch()135 protected ControllerErrorEvent doPrefetch() { 136 synchronized (this) { 137 try { 138 int stream; 139 stream = sourceLocator.getPrimaryAudioStreamNumber(); 140 if (stream > 0) 141 Libbluray.writePSR(RegisterAccess.PSR_AUDIO_STN, stream); 142 stream = sourceLocator.getPGTextStreamNumber(); 143 if (stream > 0) { 144 Libbluray.writePSR(RegisterAccess.PSR_PG_TXTST_STN, stream, 0x00000fff); 145 } 146 stream = sourceLocator.getSecondaryVideoStreamNumber(); 147 if (stream > 0) { 148 Libbluray.writePSR(RegisterAccess.PSR_SECONDARY_AUDIO_STN, stream << 8, 0x0000ff00); 149 } 150 stream = sourceLocator.getSecondaryAudioStreamNumber(); 151 if (stream > 0) { 152 Libbluray.writePSR(RegisterAccess.PSR_SECONDARY_AUDIO_STN, stream, 0x000000ff); 153 } 154 155 int plId = sourceLocator.getPlayListId(); 156 long time = -1; 157 int piId = -1, mark = -1; 158 if (baseMediaTime != 0) { 159 time = (long)(baseMediaTime * FROM_NAROSECONDS); 160 } /*else*/ if (sourceLocator.getMarkId() > 0) { 161 mark = sourceLocator.getMarkId(); 162 } /*else*/ if (sourceLocator.getPlayItemId() > 0) { 163 piId = sourceLocator.getPlayItemId(); 164 } 165 166 if (!Libbluray.selectPlaylist(plId, piId, mark, time)) { 167 return new ConnectionErrorEvent(this); 168 } 169 170 updateTime(new Time(Libbluray.tellTime() * TO_SECONDS)); 171 172 currentLocator = new BDLocator(sourceLocator.toExternalForm()); 173 } catch (Exception e) { 174 return new ConnectionErrorEvent(this); 175 } 176 return super.doPrefetch(); 177 } 178 } 179 doStart(Time at)180 protected ControllerErrorEvent doStart(Time at) { 181 synchronized (this) { 182 if (at != null) { 183 try { 184 Libbluray.seekTime((long)(at.getSeconds() * FROM_SECONDS)); 185 } catch (Exception e) { 186 return new ConnectionErrorEvent(this); 187 } 188 } 189 190 try { 191 Libbluray.selectRate(rate, true); 192 } catch (Exception e) { 193 return new ConnectionErrorEvent(this); 194 } 195 196 at = new Time(Libbluray.tellTime() * TO_SECONDS); 197 return super.doStart(at); 198 } 199 } 200 doStop(boolean eof)201 protected ControllerErrorEvent doStop(boolean eof) { 202 Libbluray.selectRate(0.0f, false); 203 if (!eof) { 204 Libbluray.stopPlaylist(); 205 } 206 return super.doStop(eof); 207 } 208 doSeekTime(Time at)209 protected void doSeekTime(Time at) { 210 synchronized (this) { 211 if ((state == Prefetched) || (state == Started)) { 212 try { 213 Libbluray.seekTime((long)(at.getSeconds() * FROM_SECONDS)); 214 } catch (Exception e) { 215 return; 216 } 217 at = new Time(Libbluray.tellTime() * TO_SECONDS); 218 } 219 super.doSeekTime(at); 220 } 221 } 222 doSetRate(Float factor)223 protected void doSetRate(Float factor) { 224 synchronized (this) { 225 if (state == Started) { 226 try { 227 Libbluray.selectRate(factor.floatValue()); 228 } catch (Exception e) { 229 return; 230 } 231 if (state == Started) { 232 baseMediaTime = getMediaNanoseconds(); 233 baseTime = getTimeBase().getNanoseconds(); 234 } 235 } 236 super.doSetRate(factor); 237 } 238 } 239 240 /* notification from app */ 241 postMediaSelectSucceeded(BDLocator locator)242 private void postMediaSelectSucceeded(BDLocator locator) { 243 ((DVBMediaSelectControlImpl)controls[3]).postMediaSelectSucceededEvent(new Locator[] { locator }); 244 } 245 doRateChanged(float rate)246 protected void doRateChanged(float rate) { 247 synchronized (this) { 248 if (state == Started) { 249 baseMediaTime = getMediaNanoseconds(); 250 baseTime = getTimeBase().getNanoseconds(); 251 } 252 super.doRateChanged(rate); 253 } 254 } 255 256 BDLocator lastMarkLocator = null; doChapterReached(int chapter)257 protected void doChapterReached(int chapter) { 258 if (chapter <= 0) 259 return; 260 chapter--; 261 synchronized (this) { 262 if (pi == null) 263 return; 264 org.videolan.TIMark[] marks = pi.getMarks(); 265 if (marks == null) 266 return; 267 for (int i = 0, j = 0; i < marks.length; i++) { 268 if (marks[i].getType() == org.videolan.TIMark.MARK_TYPE_ENTRY) { 269 if (j == chapter) { 270 if (currentLocator == null || lastMarkLocator != currentLocator || i != currentLocator.getMarkId()) { 271 ((PlaybackControlImpl)controls[9]).onMarkReach(i); 272 } 273 return; 274 } 275 j++; 276 } 277 } 278 } 279 } 280 doMarkReached(int param)281 protected void doMarkReached(int param) { 282 synchronized (this) { 283 ((PlaybackControlImpl)controls[9]).onMarkReach(param); 284 285 if (currentLocator != null) 286 currentLocator.setMarkId(param); 287 288 lastMarkLocator = currentLocator; 289 } 290 } 291 doPlaylistStarted(int param)292 protected void doPlaylistStarted(int param) { 293 } 294 doPlayItemReached(int param)295 protected void doPlayItemReached(int param) { 296 ((PlaybackControlImpl)controls[9]).onPlayItemReach(param); 297 ((UOMaskTableControlImpl)controls[16]).onPlayItemReach(param); 298 299 300 if (currentLocator != null) { 301 currentLocator.setPlayItemId(param); 302 postMediaSelectSucceeded(currentLocator); 303 } 304 305 try { 306 ((TitleContextImpl)ServiceContextFactory.getInstance().getServiceContext(null)).presentationChanged(); 307 } catch (Exception e) { 308 System.err.println("" + e + "\n" + Logger.dumpStack(e)); 309 } 310 311 if (pi != null) { 312 TIClip[] clips = pi.getClips(); 313 if (clips != null && param >= 0 && param < clips.length) { 314 ((SubtitlingControlImpl)controls[15]).onSubtitleAvailable(clips[param].getPgStreamCount() > 0); 315 } 316 } 317 } 318 doUOMasked(int position)319 protected void doUOMasked(int position) { 320 ((UOMaskTableControlImpl)controls[16]).onUOMasked(position); 321 } 322 doAngleChanged(int param)323 protected void doAngleChanged(int param) { 324 ((AngleControlImpl)controls[0]).onAngleChange(param); 325 } 326 doSubtitleChanged(int param)327 protected void doSubtitleChanged(int param) { 328 ((SubtitlingControlImpl)controls[15]).onSubtitleChange(param); 329 330 if (currentLocator != null) { 331 currentLocator.setPGTextStreamNumber(param & 0xfff); 332 postMediaSelectSucceeded(currentLocator); 333 } 334 } 335 doAudioStreamChanged(int param)336 protected void doAudioStreamChanged(int param) { 337 if (currentLocator != null) { 338 currentLocator.setPrimaryAudioStreamNumber(param); 339 postMediaSelectSucceeded(currentLocator); 340 } 341 } 342 doSecondaryVideoChanged(int stream, boolean enable)343 private void doSecondaryVideoChanged(int stream, boolean enable) { 344 if (currentLocator != null) { 345 currentLocator.setSecondaryVideoStreamNumber(stream); 346 postMediaSelectSucceeded(currentLocator); 347 } 348 349 ((PiPControlImpl)controls[8]).onPiPStatusChange(enable); 350 } 351 doSecondaryAudioChanged(int stream, boolean enable)352 private void doSecondaryAudioChanged(int stream, boolean enable) { 353 if (currentLocator != null) { 354 currentLocator.setSecondaryAudioStreamNumber(stream); 355 postMediaSelectSucceeded(currentLocator); 356 } 357 } 358 doSecondaryStreamChanged(int param)359 protected void doSecondaryStreamChanged(int param) { 360 doSecondaryVideoChanged((param & 0xff00) >> 8, (param & 0x80000000) != 0); 361 doSecondaryAudioChanged(param & 0xff, (param & 0x40000000) != 0); 362 } 363 doEndOfMediaReached(int playlist)364 protected void doEndOfMediaReached(int playlist) { 365 synchronized (this) { 366 if (currentLocator == null) { 367 logger.error("endOfMedia(" + playlist + ") ignored: no current locator"); 368 return; 369 } 370 if (currentLocator.getPlayListId() != playlist) { 371 logger.error("endOfMedia ignored: playlist does not match (" + playlist + " != " + currentLocator.getPlayListId()); 372 return; 373 } 374 } 375 376 super.doEndOfMediaReached(playlist); 377 } 378 doSeekNotify(long tick)379 protected void doSeekNotify(long tick) { 380 super.doSeekNotify(Libbluray.tellTime()); 381 } 382 383 /* used by DVBMediaSelectControlImpl */ getCurrentLocator()384 protected BDLocator getCurrentLocator() { 385 if (currentLocator != null) 386 return currentLocator; 387 return sourceLocator; 388 } 389 getPlaylistInfo()390 protected PlaylistInfo getPlaylistInfo() { 391 return pi; 392 } 393 getCurrentClipInfo()394 protected TIClip getCurrentClipInfo() { 395 synchronized (this) { 396 int state = getState(); 397 if ((state != Prefetched) && (state != Started)) 398 return null; 399 400 int playitem = RegisterAccess.getInstance().getPSR(RegisterAccess.PSR_PLAYITEM_ID); 401 TIClip[] clips = pi.getClips(); 402 if (playitem >= clips.length) 403 return null; 404 return clips[playitem]; 405 } 406 } 407 selectPlayList(BDLocator locator)408 protected void selectPlayList(BDLocator locator) 409 throws InvalidPlayListException, InvalidLocatorException, ClockStartedError { 410 411 if (locator == null) { 412 logger.error("null locator"); 413 throw new NullPointerException(); 414 } 415 if (!locator.isPlayListItem()) { 416 logger.error("not playlist: " + locator); 417 throw new InvalidLocatorException(locator); 418 } 419 420 PlaylistInfo newPi = Libbluray.getPlaylistInfo(locator.getPlayListId()); 421 if (newPi == null) { 422 logger.error("invalid playlist"); 423 throw new InvalidPlayListException(); 424 } 425 426 synchronized (this) { 427 if (getState() == Started) { 428 logger.error("throw ClockStartedError"); 429 throw new ClockStartedError(); 430 } 431 432 this.pi = newPi; 433 this.sourceLocator = locator; 434 this.currentLocator = null; 435 436 baseMediaTime = 0; 437 if (state == Prefetched) 438 doPrefetch(); 439 } 440 } 441 seekMark(int mark)442 protected void seekMark(int mark) throws IllegalArgumentException { 443 if ((pi == null) || (mark < 0) || (mark >= pi.getMarkCount())) 444 throw new IllegalArgumentException(); 445 PlaylistPlayerAction action = new PlaylistPlayerAction( 446 this, PlaylistPlayerAction.ACTION_SEEK_MARK, mark); 447 commandQueue.put(action); 448 action.waitEnd(); 449 } 450 seekPlayItem(int item)451 protected void seekPlayItem(int item) throws IllegalArgumentException { 452 if ((pi == null) || (item < 0) || (item >= pi.getClipCount())) 453 throw new IllegalArgumentException(); 454 PlaylistPlayerAction action = new PlaylistPlayerAction( 455 this, PlaylistPlayerAction.ACTION_SEEK_PLAYITEM, item); 456 commandQueue.put(action); 457 action.waitEnd(); 458 } 459 460 private static class PlaylistPlayerAction extends BDJAction { PlaylistPlayerAction(Handler player, int action, int param)461 private PlaylistPlayerAction(Handler player, int action, int param) { 462 this.player = player; 463 this.action = action; 464 this.param = param; 465 } 466 doAction()467 protected void doAction() { 468 switch (action) { 469 case ACTION_SEEK_MARK: 470 if ((player.getState() == Prefetched) || (player.getState() == Started)) { 471 Libbluray.seekMark(param); 472 player.updateTime(new Time(Libbluray.tellTime() * TO_SECONDS)); 473 } else if (player.sourceLocator != null) { 474 player.sourceLocator.setMarkId(param); 475 player.sourceLocator.setPlayItemId(-1); 476 } 477 break; 478 case ACTION_SEEK_PLAYITEM: 479 if ((player.getState() == Prefetched) || (player.getState() == Started)) { 480 Libbluray.seekPlayItem(param); 481 player.updateTime(new Time(Libbluray.tellTime() * TO_SECONDS)); 482 } else if (player.sourceLocator != null) { 483 player.sourceLocator.setMarkId(-1); 484 player.sourceLocator.setPlayItemId(param); 485 } 486 break; 487 } 488 } 489 490 private Handler player; 491 private int action; 492 private int param; 493 494 public static final int ACTION_SEEK_MARK = 1; 495 public static final int ACTION_SEEK_PLAYITEM = 2; 496 } 497 498 private PlaylistInfo pi = null; 499 private BDLocator currentLocator = null; 500 private BDLocator sourceLocator = null; 501 502 private static final Logger logger = Logger.getLogger(Handler.class.getName()); 503 } 504