1 /*
2  * This file is part of libbluray
3  * Copyright (C) 2010  William Hahne
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library. If not, see
17  * <http://www.gnu.org/licenses/>.
18  */
19 
20 package org.videolan;
21 
22 import java.io.File;
23 import java.io.InputStream;
24 import java.io.InvalidObjectException;
25 import java.util.Enumeration;
26 import org.videolan.Logger;
27 
28 import org.bluray.net.BDLocator;
29 import org.bluray.system.RegisterAccess;
30 import org.bluray.ti.TitleImpl;
31 import org.davic.media.MediaLocator;
32 import org.dvb.application.AppID;
33 import org.dvb.application.AppsDatabase;
34 import org.dvb.application.CurrentServiceFilter;
35 
36 import javax.media.Manager;
37 import javax.tv.locator.Locator;
38 
39 import org.videolan.bdjo.AppEntry;
40 import org.videolan.bdjo.Bdjo;
41 import org.videolan.bdjo.GraphicsResolution;
42 import org.videolan.bdjo.PlayListTable;
43 import org.videolan.bdjo.TerminalInfo;
44 import org.videolan.media.content.PlayerManager;
45 
46 public class BDJLoader {
47 
48     private static class FontCacheAction extends BDJAction {
FontCacheAction(InputStream is)49         public FontCacheAction(InputStream is) {
50             this.fontPath = null;
51             this.is = is;
52         }
53 
FontCacheAction(String fontPath)54         public FontCacheAction(String fontPath) {
55             this.fontPath = fontPath;
56             this.is = null;
57         }
58 
doAction()59         protected void doAction() {
60             try {
61                 if (this.is != null) {
62                     this.cacheFile = addFontImpl(is);
63                 } else {
64                     this.cacheFile = addFontImpl(fontPath);
65                 }
66             } catch (RuntimeException e) {
67                 this.exception = e;
68             }
69         }
70 
execute()71         public File execute() {
72             BDJActionManager.getInstance().putCommand(this);
73             waitEnd();
74             if (exception != null) {
75                 throw exception;
76             }
77             return cacheFile;
78         }
79 
80         private final String fontPath;
81         private final InputStream is;
82         private File cacheFile = null;
83         private RuntimeException exception = null;
84     }
85 
86     /* called by org.dvb.ui.FontFactory */
addFont(InputStream is)87     public static File addFont(InputStream is) {
88         if (BDJXletContext.getCurrentContext() == null)
89             return addFontImpl(is);
90         /* dispatch cache request to privileged thread */
91         return new FontCacheAction(is).execute();
92     }
93 
94     /* called by org.dvb.ui.FontFactory */
addFont(String fontFile)95     public static File addFont(String fontFile) {
96         if (BDJXletContext.getCurrentContext() == null)
97             return addFontImpl(fontFile);
98         /* dispatch cache request to privileged thread */
99         return new FontCacheAction(fontFile).execute();
100     }
101 
addFontImpl(InputStream is)102     private static File addFontImpl(InputStream is) {
103         VFSCache localCache = vfsCache;
104         if (localCache != null) {
105             return localCache.addFont(is);
106         }
107         return null;
108     }
109 
addFontImpl(String fontFile)110     private static File addFontImpl(String fontFile) {
111         VFSCache localCache = vfsCache;
112         if (localCache != null) {
113             return localCache.addFont(fontFile);
114         }
115         return null;
116     }
117 
118     /* called by BDJSecurityManager */
accessFile(String file)119     protected static void accessFile(String file) {
120         VFSCache localCache = vfsCache;
121         if (localCache != null) {
122             localCache.accessFile(file);
123         }
124     }
125 
getCachedFile(String path)126     public static String getCachedFile(String path) {
127         VFSCache localCache = vfsCache;
128         if (localCache != null) {
129             return localCache.map(path);
130         }
131         return path;
132     }
133 
load(TitleImpl title, boolean restart, BDJLoaderCallback callback)134     public static boolean load(TitleImpl title, boolean restart, BDJLoaderCallback callback) {
135         // This method should be called only from ServiceContextFactory
136 
137         if (title == null)
138             return false;
139         synchronized (BDJLoader.class) {
140             if (queue == null)
141                 queue = BDJActionQueue.create("BDJLoader");
142         }
143         queue.put(new BDJLoaderAction(title, restart, callback));
144         return true;
145     }
146 
unload(BDJLoaderCallback callback)147     public static boolean unload(BDJLoaderCallback callback) {
148         // This method should be called only from ServiceContextFactory
149 
150         synchronized (BDJLoader.class) {
151             if (queue == null)
152                 queue = BDJActionQueue.create("BDJLoader");
153         }
154         queue.put(new BDJLoaderAction(null, false, callback));
155         return true;
156     }
157 
shutdown()158     protected static void shutdown() {
159         try {
160             if (queue != null) {
161                 queue.shutdown();
162             }
163         } catch (Throwable e) {
164             logger.error("shutdown() failed: " + e + "\n" + Logger.dumpStack(e));
165         }
166         queue = null;
167         vfsCache = null;
168     }
169 
loadN(TitleImpl title, boolean restart)170     private static boolean loadN(TitleImpl title, boolean restart) {
171 
172         if (vfsCache == null) {
173             vfsCache = VFSCache.createInstance();
174         }
175 
176         TitleInfo ti = title.getTitleInfo();
177         if (!ti.isBdj()) {
178             logger.info("Not BD-J title - requesting HDMV title start");
179             unloadN();
180             return Libbluray.selectHdmvTitle(title.getTitleNum());
181         }
182 
183         try {
184             // load bdjo
185             Bdjo bdjo = Libbluray.getBdjo(ti.getBdjoName());
186             if (bdjo == null)
187                 throw new InvalidObjectException("bdjo not loaded");
188             AppEntry[] appTable = bdjo.getAppTable();
189 
190             // reuse appProxys
191             BDJAppProxy[] proxys = new BDJAppProxy[appTable.length];
192             AppsDatabase db = AppsDatabase.getAppsDatabase();
193             Enumeration ids = db.getAppIDs(new CurrentServiceFilter());
194             while (ids.hasMoreElements()) {
195                 AppID id = (AppID)ids.nextElement();
196                 BDJAppProxy proxy = (BDJAppProxy)db.getAppProxy(id);
197                 AppEntry entry = (AppEntry)db.getAppAttributes(id);
198                 if (proxy == null) {
199                     logger.error("AppsDatabase corrupted!");
200                     continue;
201                 }
202                 if (entry == null) {
203                     logger.error("AppsDatabase corrupted!");
204                     proxy.release();
205                     continue;
206                 }
207                 for (int i = 0; i < appTable.length; i++) {
208                     if (id.equals(appTable[i].getIdentifier()) &&
209                         entry.getInitialClass().equals(appTable[i].getInitialClass())) {
210                         if (restart && appTable[i].getIsServiceBound()) {
211                             logger.info("Stopping xlet " + appTable[i].getInitialClass() + " (for restart)");
212                             proxy.stop(true);
213                         } else {
214                             logger.info("Keeping xlet " + appTable[i].getInitialClass());
215                             proxys[i] = proxy;
216                             proxy = null;
217                         }
218                         break;
219                     }
220                 }
221                 if (proxy != null) {
222                     logger.info("Terminating xlet " + entry.getInitialClass());
223                     proxy.release();
224                 }
225             }
226 
227             // start bdj window
228             GUIManager gui = GUIManager.createInstance();
229             TerminalInfo terminfo = bdjo.getTerminalInfo();
230             GraphicsResolution res = terminfo.getResolution();
231             gui.setDefaultFont(terminfo.getDefaultFont());
232             gui.setResizable(true);
233             gui.setSize(res.getWidth(), res.getHeight());
234             gui.setVisible(true);
235 
236             Libbluray.setUOMask(terminfo.getMenuCallMask(), terminfo.getTitleSearchMask());
237             Libbluray.setKeyInterest(bdjo.getKeyInterestTable());
238 
239             // initialize AppCaches
240             if (vfsCache != null) {
241                 vfsCache.add(bdjo.getAppCaches());
242             }
243 
244             try {
245                 BDJLoaderAdapter a = Libbluray.getLoaderAdapter();
246                 if (a != null)
247                     appTable = a.patchAppTable(appTable, title.getTitleNum());
248             } catch (Throwable t) {
249                 logger.error("" + t);
250             }
251 
252             // initialize appProxys
253             for (int i = 0; i < appTable.length; i++) {
254                 if (proxys[i] == null) {
255                     proxys[i] = BDJAppProxy.newInstance(new BDJXletContext(appTable[i], bdjo.getAppCaches(), gui));
256 
257                     /* log startup class, startup parameters and jar file */
258                     String[] params = appTable[i].getParams();
259                     String p = "";
260                     if (params != null && params.length > 0) {
261                         p = "(" + StrUtil.Join(params, ",") + ")";
262                     }
263                     logger.info("Loaded class: " + appTable[i].getInitialClass() + p + " from " + appTable[i].getBasePath() + ".jar");
264                 } else {
265                     proxys[i].getXletContext().update(appTable[i], bdjo.getAppCaches());
266                     logger.info("Reused class: " + appTable[i].getInitialClass() +     " from " + appTable[i].getBasePath() + ".jar");
267                 }
268             }
269 
270             // change psr
271             Libbluray.writePSR(RegisterAccess.PSR_TITLE_NR, title.getTitleNum());
272 
273             // notify AppsDatabase
274             ((BDJAppsDatabase)BDJAppsDatabase.getAppsDatabase()).newDatabase(bdjo, proxys);
275 
276             // auto start playlist
277             try {
278                 PlayListTable plt = bdjo.getAccessiblePlaylists();
279                 if ((plt != null) && (plt.isAutostartFirst())) {
280                     logger.info("Auto-starting playlist");
281                     String[] pl = plt.getPlayLists();
282                     if (pl.length > 0)
283                         Manager.createPlayer(new MediaLocator(new BDLocator("bd://PLAYLIST:" + pl[0]))).start();
284                 }
285             } catch (Exception e) {
286                 logger.error("loadN(): autoplaylist failed: " + e + "\n" + Logger.dumpStack(e));
287             }
288 
289             // now run all the xlets
290             for (int i = 0; i < appTable.length; i++) {
291                 int code = appTable[i].getControlCode();
292                 if (code == AppEntry.AUTOSTART) {
293                     logger.info("Autostart xlet " + i + ": " + appTable[i].getInitialClass());
294                     proxys[i].start();
295                 } else if (code == AppEntry.PRESENT) {
296                     logger.info("Init xlet " + i + ": " + appTable[i].getInitialClass());
297                     proxys[i].init();
298                 } else {
299                     logger.info("Unsupported xlet code (" +code+") xlet " + i + ": " + appTable[i].getInitialClass());
300                 }
301             }
302 
303             logger.info("Finished initializing and starting xlets.");
304 
305             return true;
306 
307         } catch (Throwable e) {
308             logger.error("loadN() failed: " + e + "\n" + Logger.dumpStack(e));
309             unloadN();
310             return false;
311         }
312     }
313 
unloadN()314     private static boolean unloadN() {
315         try {
316             try {
317                 GUIManager.getInstance().setVisible(false);
318             } catch (Error e) {
319             }
320 
321             AppsDatabase db = AppsDatabase.getAppsDatabase();
322 
323             /* stop xlets first */
324             Enumeration ids = db.getAppIDs(new CurrentServiceFilter());
325             while (ids.hasMoreElements()) {
326                 AppID id = (AppID)ids.nextElement();
327                 BDJAppProxy proxy = (BDJAppProxy)db.getAppProxy(id);
328                 if (proxy != null) {
329                     proxy.stop(true);
330                 }
331             }
332 
333             ids = db.getAppIDs(new CurrentServiceFilter());
334             while (ids.hasMoreElements()) {
335                 AppID id = (AppID)ids.nextElement();
336                 BDJAppProxy proxy = (BDJAppProxy)db.getAppProxy(id);
337                 if (proxy != null) {
338                     proxy.release();
339                 }
340             }
341 
342             ((BDJAppsDatabase)db).newDatabase(null, null);
343 
344             PlayerManager.getInstance().releaseAllPlayers(true);
345 
346             return true;
347         } catch (Throwable e) {
348             logger.error("unloadN() failed: " + e + "\n" + Logger.dumpStack(e));
349             return false;
350         }
351     }
352 
353     private static class BDJLoaderAction extends BDJAction {
BDJLoaderAction(TitleImpl title, boolean restart, BDJLoaderCallback callback)354         public BDJLoaderAction(TitleImpl title, boolean restart, BDJLoaderCallback callback) {
355             this.title = title;
356             this.restart = restart;
357             this.callback = callback;
358         }
359 
doAction()360         protected void doAction() {
361             boolean succeed;
362             if (title != null)
363                 succeed = loadN(title, restart);
364             else
365                 succeed = unloadN();
366             if (callback != null)
367                 callback.loaderDone(succeed);
368         }
369 
370         private TitleImpl title;
371         private boolean restart;
372         private BDJLoaderCallback callback;
373     }
374 
375     private static final Logger logger = Logger.getLogger(BDJLoader.class.getName());
376 
377     private static BDJActionQueue queue = null;
378     private static VFSCache       vfsCache = null;
379 }
380