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