001 ///////////////////////////////////////////////// 002 // This file is part of Sears project. 003 // Subtitle Editor And Re-Synch 004 // A tool to easily modify and resynch movies subtitles. 005 ///////////////////////////////////////////////// 006 //This program is free software; 007 //you can redistribute it and/or modify it under the terms 008 //of the GNU General Public License 009 //as published by the Free Software Foundation; 010 //either version 2 of the License, or (at your option) any later version. 011 ///////////////////////////////////////////////// 012 //Sears project is available under sourceforge 013 // at adress: http://sourceforge.net/projects/sears/ 014 //Copyright (C) 2005 Booba Skaya 015 //Mail: booba.skaya@gmail.com 016 //////////////////////////////////////////////// 017 018 package sears.tools.player; 019 020 import java.io.BufferedReader; 021 import java.io.File; 022 import java.io.FileOutputStream; 023 import java.io.IOException; 024 import java.io.InputStream; 025 import java.io.InputStreamReader; 026 import java.io.OutputStream; 027 import java.net.URL; 028 import java.net.URLConnection; 029 import sears.gui.MainWindow; 030 import sears.tools.SearsProperties; 031 import sears.tools.Trace; 032 import sears.tools.Utils; 033 import sun.misc.BASE64Encoder; 034 035 /** 036 * Class VLCPlayer. 037 * <br><b>Summary:</b><br> 038 * This class implements the PlayerInterface, and is designed to control the vlc software. 039 * VLC is controled through its http server mode. 040 * You can found vlc at http://www.videolan.org/ 041 * @author David DEBARGE 042 */ 043 public class VLCPlayer implements PlayerInterface { 044 045 /**The VLC parameter which is used to exec VLC player*/ 046 public static String vlcParameter; 047 048 /**The hostname uses to connect to VLC in remote control mode*/ 049 private static String hostName = "localhost"; 050 051 /**The default port number uses to connect to VLC in remote control mode*/ 052 private static final int DEFAULT_PORT = 8080; 053 054 /**The port number uses to connect to VLC in remote control mode*/ 055 private static int portNumber; 056 057 /**The VLC exec process*/ 058 private static Process vlcProcess = null; 059 060 /**The VLC current video file name*/ 061 private static String currentVideoFile = null; 062 063 /**The VLC exec process input stream thread*/ 064 private static Thread vlcInputStreamThread = null; 065 066 /**The VLC exec process error stream thread*/ 067 private static Thread vlcErrorStreamThread = null; 068 069 /**The temporary subtitle file which is used by VLC*/ 070 private static File vlcSubtitleFile = null; 071 072 /**The temporary logo file*/ 073 private static File vlcLogoFile = null; 074 075 /**The default VLC restart parameter*/ 076 public static final String DEFAULT_VLC_RESTART = "0"; 077 078 /**The empty playlist http request */ 079 private static final String EMPTY_PLAYLIST_REQUEST = "/?control=empty"; 080 /**The add playlist http request */ 081 private static final String ADD_PLAYLIST_REQUEST = "/?control=add&mrl="; 082 /**The play http request */ 083 private static final String PLAY_REQUEST = "/?control=play&item=0"; 084 /**The replay http request */ 085 private static final String REPLAY_REQUEST = "/?control=next"; 086 /**The pause http request */ 087 private static final String PAUSE_REQUEST = "/?control=pause"; 088 // /**The unpause http request */ 089 // private static final String UNPAUSE_REQUEST = "/?control=pause"; 090 /**The stop http request */ 091 private static final String STOP_REQUEST = "/?control=stop"; 092 /**The seek http request */ 093 private static final String SEEK_REQUEST = "/?control=seek&seek_value="; 094 /**The get time http request */ 095 private static final String GET_TIME_REQUEST = "/old/admin/dboxfiles.html?stream_time=true"; 096 /**The get length http request */ 097 private static final String GET_LENGTH_REQUEST = "/old/admin/dboxfiles.html?stream_length=true"; 098 /**The quit http request */ 099 private static final String QUIT_REQUEST = "/old/admin/?control=shutdown"; 100 101 public static final String DEFAULT_VLC_PATH_MAC = "/Applications/VLC.app/Contents/MacOS/VLC"; 102 103 public static final String DEFAULT_VLC_PATH_LINUX = "/usr/bin/vlc"; 104 105 public static final String DEFAULT_VLC_PATH_WINDOWS = "C:\\Program Files\\VideoLAN\\VLC"; 106 107 108 /** 109 * Method constructor. 110 * <br><b>Summary:</b><br> 111 * The class constructor 112 * @param hostName The <b>String</b> hostname to connect to VLC 113 * @param portNumber The <b>int</b> port number to connect to VLC 114 */ 115 public VLCPlayer(String hostName, int portNumber) 116 throws PlayerException { 117 VLCPlayer.hostName = hostName; 118 //Retrieve the port number from properties. 119 String portString = SearsProperties.getProperty(SearsProperties.VLC_PORT); 120 //If cannot found teh property, set is as default. 121 if(portString == null || portString.equals("")){ 122 portString = ""+DEFAULT_PORT; 123 //do not forget to set it in the properties. 124 SearsProperties.setProperty(SearsProperties.VLC_PORT, ""+DEFAULT_PORT); 125 } 126 try{ 127 //try to read an integer in the port string value. 128 VLCPlayer.portNumber = Integer.parseInt(SearsProperties.getProperty(SearsProperties.VLC_PORT, ""+VLCPlayer.DEFAULT_PORT)); 129 }catch (NumberFormatException e){ 130 //If can't read an int, set it as default, and rewrite it in the properties. 131 VLCPlayer.portNumber = DEFAULT_PORT; 132 SearsProperties.setProperty(SearsProperties.VLC_PORT, ""+DEFAULT_PORT); 133 } 134 135 // 136 // Create temporary files 137 // 138 try { 139 vlcSubtitleFile = java.io.File.createTempFile("vlcSubtitle", null); 140 vlcSubtitleFile.deleteOnExit(); 141 vlcLogoFile = java.io.File.createTempFile("vlcLogo", ".png"); 142 vlcLogoFile.deleteOnExit(); 143 } catch (IOException e) { 144 throw new PlayerException("Cannot create the temporary files for VLC"); 145 } 146 147 // 148 // Set the VLC parameters 149 // 150 vlcParameter 151 = " --nofullscreen" 152 + " --osd" 153 + " --no-sub-autodetect-file" 154 // + " --extraintf http" 155 + " --intf http" 156 // Create the http host parameter of VLC 157 + " --http-host " + hostName + ":" + portNumber 158 + " --sub-file " + vlcSubtitleFile.getAbsolutePath(); 159 160 // 161 // Copy logo file in temporary file 162 // 163 try { 164 InputStream in = MainWindow.instance.getClass() 165 .getResourceAsStream("/sears/gui/resources/sears.png"); 166 OutputStream out = new FileOutputStream(vlcLogoFile); 167 Utils.copyStream(in,out); 168 vlcParameter 169 += " --logo-file " + vlcLogoFile.getAbsolutePath() 170 + " --sub-filter logo" 171 + " --logo-position 6" 172 + " --logo-transparency 30"; 173 } catch (Exception e) { 174 Trace.trace("Cannot copy the logo file:"+e.getMessage()); 175 vlcLogoFile = null; 176 } 177 } 178 179 /** 180 * Method constructor. 181 * The class constructor using the default value of hostName and portNumber 182 * to connect to VLC 183 * <br><b>Summary:</b><br> 184 */ 185 public VLCPlayer() throws PlayerException { 186 this(hostName, portNumber); 187 } 188 189 /** 190 * Method sendRequest. 191 * <br><b>Summary:</b><br> 192 * Send http request to VLC. 193 * @param request <b>String</b> the http request. 194 * @param useAuthorization <b>boolean</b> use Authorization. 195 * @param getResponse <b>boolean</b> attempt response or not. 196 * @return Response <b>String</b> that correspond to request response. 197 */ 198 private synchronized static String sendRequest(String request, boolean useAuthorization, boolean getResponse) { 199 String response = ""; 200 try { 201 // connect to the URL 202 URL url = new URL("http://"+hostName+":"+portNumber+request); 203 URLConnection urlC = url.openConnection(); 204 // use or not the login and password 205 if(useAuthorization) { 206 String loginPassword = "admin:admin"; 207 BASE64Encoder enc = new sun.misc.BASE64Encoder(); 208 urlC.setRequestProperty("Authorization", "Basic " 209 + enc.encode(loginPassword.getBytes())); 210 } 211 // Extract response from stream 212 InputStream content = (InputStream) urlC.getInputStream(); 213 BufferedReader in = new BufferedReader(new InputStreamReader(content)); 214 if(getResponse) { 215 int car = in.read(); 216 while((car != -1) && (car != (int) 'D')) { 217 response += new Character((char)car).toString(); 218 car = in.read(); 219 } 220 } 221 // Empty buffered read 222 while ((in.readLine()) != null) {} 223 } catch (Exception e) { 224 System.err.println(e.getMessage()); 225 } 226 return response; 227 } 228 229 /** 230 * Method vlcIsRunning. 231 * <br><b>Summary:</b><br> 232 * Check if the VLC program is running or not. 233 * @return isRunning <b>boolean</b> is running or not. 234 */ 235 private static boolean isRunning() { 236 boolean vlcIsRunning = false; 237 if (vlcProcess != null) { 238 try { 239 vlcProcess.exitValue(); 240 } catch (IllegalThreadStateException e) { 241 vlcIsRunning = true; 242 } 243 } 244 return vlcIsRunning; 245 } 246 247 /* 248 * (non-Javadoc) 249 * 250 * @see sears.tools.player.PlayerInterface#play(java.lang.String, 251 * java.lang.String) 252 */ 253 public void play(String videoFile, String subtitleFile) 254 throws PlayerException { 255 if (videoFile != null) { 256 int videoPosition = -1; 257 258 // 259 // Get the video position to restore it 260 // 261 if (isRunning()) { 262 videoPosition = getPosition(); 263 } 264 265 // 266 // Restart VLC player or stop it 267 // 268 if (SearsProperties.getProperty(SearsProperties.VLC_RESTART, VLCPlayer.DEFAULT_VLC_RESTART).equals("1")) { 269 quit(); 270 } 271 272 // 273 // Copy the new subtitle file to the VLC subtitle file 274 // 275 try { 276 if (subtitleFile != null) { 277 Utils.copyFile(new File(subtitleFile),vlcSubtitleFile); 278 } else { 279 // if no subtitle file exists then empty the subtitle file 280 OutputStream out = new FileOutputStream(vlcSubtitleFile); 281 out.close(); 282 } 283 } catch (IOException e) { 284 throw new PlayerException("Cannot copy the subtitle file"); 285 } 286 287 // 288 // launch VLC program and the TCP client 289 // 290 if (!isRunning()) { 291 // Clean all resources 292 quit(); 293 // Start VLC player 294 String vlcFile = SearsProperties.getProperty(SearsProperties.PLAYER_FULL_PATH); 295 try { 296 vlcProcess = Runtime.getRuntime().exec(vlcFile + " " + vlcParameter); 297 Thread.sleep(4000); 298 299 // Start the input stream thread 300 vlcInputStreamThread = new Thread("vlcInputStream") { 301 public void run() { 302 try { 303 BufferedReader is = new BufferedReader(new InputStreamReader(vlcProcess.getInputStream())); 304 while ((is.readLine()) != null) {} 305 } catch (IOException e) { 306 Trace.trace("VLC input stream thread failed:"+e.getMessage()); 307 } 308 } 309 }; 310 vlcInputStreamThread.start(); 311 312 // Start the error stream thread 313 vlcErrorStreamThread = new Thread("vlcErrorStream") { 314 public void run() { 315 try { 316 BufferedReader is = new BufferedReader(new InputStreamReader(vlcProcess.getErrorStream())); 317 while ((is.readLine()) != null) {} 318 } catch (IOException e) { 319 Trace.trace("VLC error stream thread failed:"+e.getMessage()); 320 } 321 } 322 }; 323 vlcErrorStreamThread.start(); 324 } catch (Exception e) { 325 quit(); 326 throw new PlayerException("Cannot run the VLC program:"+vlcFile); 327 } 328 } 329 330 // 331 // Load video file 332 // 333 if((currentVideoFile == null) || (currentVideoFile != videoFile)) { 334 sendRequest(EMPTY_PLAYLIST_REQUEST, false, false); 335 sendRequest(ADD_PLAYLIST_REQUEST+videoFile.replaceAll(" ","+"), false, false); 336 currentVideoFile = videoFile; 337 } else { 338 sendRequest(REPLAY_REQUEST, false, false); 339 } 340 341 // 342 // Restore the video position 343 // 344 if ((videoPosition != -1) && (videoPosition != 0)) { 345 videoPosition -= 5; // reduce video position 346 if(videoPosition < 0) 347 videoPosition = 0; 348 setPosition(videoPosition); 349 } 350 351 // 352 // Play video file 353 // 354 sendRequest(PLAY_REQUEST, false, false); 355 } 356 } 357 358 /* 359 * (non-Javadoc) 360 * 361 * @see sears.tools.player.PlayerInterface#goToOffset(int) 362 */ 363 public void goToOffset(int offset) throws PlayerException { 364 // TODO Auto-generated method stub 365 } 366 367 /* 368 * (non-Javadoc) 369 * 370 * @see sears.tools.player.PlayerInterface#quit() 371 */ 372 public void quit() { 373 // reset video file name 374 currentVideoFile = null; 375 376 // Quit VLC player 377 if (vlcProcess != null) { 378 try { 379 vlcProcess.exitValue(); 380 } catch (IllegalThreadStateException e) { 381 sendRequest(QUIT_REQUEST, true, false); 382 } 383 } 384 385 // Stop VLC input stream thread 386 if(vlcInputStreamThread != null) { 387 vlcInputStreamThread.interrupt(); 388 vlcInputStreamThread = null; 389 } 390 391 // Stop VLC error stream thread 392 if(vlcErrorStreamThread != null) { 393 vlcErrorStreamThread.interrupt(); 394 vlcErrorStreamThread = null; 395 } 396 397 // Stop VLC process 398 if (vlcProcess != null) { 399 try { 400 vlcProcess.destroy(); 401 } catch (Exception e) {} 402 vlcProcess = null; 403 } 404 } 405 406 /* (non-Javadoc) 407 * @see sears.tools.player.PlayerInterface#getPosition() 408 */ 409 public int getPosition() throws PlayerException { 410 int timeInSecond = -1; 411 if (isRunning()) { 412 String response = sendRequest(GET_TIME_REQUEST, true, true); 413 timeInSecond = Integer.valueOf(response).intValue(); 414 } 415 return timeInSecond; 416 } 417 418 /* (non-Javadoc) 419 * @see sears.tools.player.PlayerInterface#getPosition() 420 */ 421 public int getLength() throws PlayerException { 422 int timeInSecond = -1; 423 if (isRunning()) { 424 String response = sendRequest(GET_LENGTH_REQUEST, true, true); 425 timeInSecond = Integer.valueOf(response).intValue(); 426 } 427 return timeInSecond; 428 } 429 430 /* (non-Javadoc) 431 * @see sears.tools.player.PlayerInterface#pause() 432 */ 433 public void pause() throws PlayerException { 434 if (isRunning()) { 435 sendRequest(PAUSE_REQUEST, true, false); 436 } 437 } 438 439 /* (non-Javadoc) 440 * @see sears.tools.player.PlayerInterface#pause() 441 */ 442 public void stop() throws PlayerException { 443 if (isRunning()) { 444 sendRequest(STOP_REQUEST, true, false); 445 } 446 } 447 448 /* (non-Javadoc) 449 * @see sears.tools.player.PlayerInterface#setPosition(int) 450 */ 451 public void setPosition(int offset) throws PlayerException { 452 if (isRunning()) { 453 sendRequest(SEEK_REQUEST+Integer.toString(offset), true, false); 454 } 455 } 456 } 457 458 459