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 /** default VLC path on Mac OS X os*/ 102 public static final String DEFAULT_VLC_PATH_MAC = "/Applications/VLC.app/Contents/MacOS/VLC"; 103 /** default VLC path in Window os */ 104 public static final String DEFAULT_VLC_PATH_LINUX = "/usr/bin/vlc"; 105 /** default VLC path in Linux os*/ 106 public static final String DEFAULT_VLC_PATH_WINDOWS = "C:\\Program Files\\VideoLAN\\VLC"; 107 108 109 /** 110 * Method constructor. 111 * <br><b>Summary:</b><br> 112 * The class constructor 113 * @param hostName The <b>String</b> hostname to connect to VLC 114 * @param portNumber The <b>int</b> port number to connect to VLC 115 * @throws PlayerException if an error occurs 116 */ 117 public VLCPlayer(String hostName, int portNumber) 118 throws PlayerException { 119 VLCPlayer.hostName = hostName; 120 //Retrieve the port number from properties. 121 String portString = SearsProperties.getProperty(SearsProperties.VLC_PORT); 122 //If cannot found teh property, set is as default. 123 if(portString == null || portString.equals("")){ 124 portString = ""+DEFAULT_PORT; 125 //do not forget to set it in the properties. 126 SearsProperties.setProperty(SearsProperties.VLC_PORT, ""+DEFAULT_PORT); 127 } 128 try{ 129 //try to read an integer in the port string value. 130 VLCPlayer.portNumber = Integer.parseInt(SearsProperties.getProperty(SearsProperties.VLC_PORT, ""+VLCPlayer.DEFAULT_PORT)); 131 }catch (NumberFormatException e){ 132 //If can't read an int, set it as default, and rewrite it in the properties. 133 VLCPlayer.portNumber = DEFAULT_PORT; 134 SearsProperties.setProperty(SearsProperties.VLC_PORT, ""+DEFAULT_PORT); 135 } 136 137 // 138 // Create temporary files 139 // 140 try { 141 vlcSubtitleFile = java.io.File.createTempFile("vlcSubtitle", null); 142 vlcSubtitleFile.deleteOnExit(); 143 vlcLogoFile = java.io.File.createTempFile("vlcLogo", ".png"); 144 vlcLogoFile.deleteOnExit(); 145 } catch (IOException e) { 146 throw new PlayerException("Cannot create the temporary files for VLC"); 147 } 148 149 // 150 // Set the VLC parameters 151 // 152 vlcParameter 153 = " --nofullscreen" 154 + " --osd" 155 + " --no-sub-autodetect-file" 156 // + " --extraintf http" 157 + " --intf http" 158 // Create the http host parameter of VLC 159 + " --http-host " + hostName + ":" + portNumber 160 + " --sub-file " + vlcSubtitleFile.getAbsolutePath(); 161 162 // 163 // Copy logo file in temporary file 164 // 165 try { 166 InputStream in = MainWindow.instance.getClass() 167 .getResourceAsStream("/sears/gui/resources/sears.png"); 168 OutputStream out = new FileOutputStream(vlcLogoFile); 169 Utils.copyStream(in,out); 170 vlcParameter 171 += " --logo-file " + vlcLogoFile.getAbsolutePath() 172 + " --sub-filter logo" 173 + " --logo-position 6" 174 + " --logo-transparency 30"; 175 } catch (Exception e) { 176 Trace.trace("Cannot copy the logo file:"+e.getMessage()); 177 vlcLogoFile = null; 178 } 179 } 180 181 /** 182 * Method constructor. 183 * The class constructor using the default value of hostName and portNumber 184 * to connect to VLC 185 * <br><b>Summary:</b><br> 186 * @throws PlayerException if an error occurs 187 */ 188 public VLCPlayer() throws PlayerException { 189 this(hostName, portNumber); 190 } 191 192 /** 193 * Method sendRequest. 194 * <br><b>Summary:</b><br> 195 * Send http request to VLC. 196 * @param request <b>String</b> the http request. 197 * @param useAuthorization <b>boolean</b> use Authorization. 198 * @param getResponse <b>boolean</b> attempt response or not. 199 * @return Response <b>String</b> that correspond to request response. 200 */ 201 private synchronized static String sendRequest(String request, boolean useAuthorization, boolean getResponse) { 202 String response = ""; 203 try { 204 // connect to the URL 205 URL url = new URL("http://"+hostName+":"+portNumber+request); 206 URLConnection urlC = url.openConnection(); 207 // use or not the login and password 208 if(useAuthorization) { 209 String loginPassword = "admin:admin"; 210 BASE64Encoder enc = new sun.misc.BASE64Encoder(); 211 urlC.setRequestProperty("Authorization", "Basic " 212 + enc.encode(loginPassword.getBytes())); 213 } 214 // Extract response from stream 215 InputStream content = (InputStream) urlC.getInputStream(); 216 BufferedReader in = new BufferedReader(new InputStreamReader(content)); 217 if(getResponse) { 218 int car = in.read(); 219 while((car != -1) && (car != (int) 'D')) { 220 response += new Character((char)car).toString(); 221 car = in.read(); 222 } 223 } 224 // Empty buffered read 225 while ((in.readLine()) != null) {} 226 } catch (Exception e) { 227 System.err.println(e.getMessage()); 228 } 229 return response; 230 } 231 232 /** 233 * Method vlcIsRunning. 234 * <br><b>Summary:</b><br> 235 * Check if the VLC program is running or not. 236 * @return isRunning <b>boolean</b> is running or not. 237 */ 238 private static boolean isRunning() { 239 boolean vlcIsRunning = false; 240 if (vlcProcess != null) { 241 try { 242 vlcProcess.exitValue(); 243 } catch (IllegalThreadStateException e) { 244 vlcIsRunning = true; 245 } 246 } 247 return vlcIsRunning; 248 } 249 250 /* 251 * (non-Javadoc) 252 * 253 * @see sears.tools.player.PlayerInterface#play(java.lang.String, 254 * java.lang.String) 255 */ 256 public void play(String videoFile, String subtitleFile) 257 throws PlayerException { 258 if (videoFile != null) { 259 int videoPosition = -1; 260 261 // 262 // Get the video position to restore it 263 // 264 if (isRunning()) { 265 videoPosition = getPosition(); 266 } 267 268 // 269 // Restart VLC player or stop it 270 // 271 if (SearsProperties.getProperty(SearsProperties.VLC_RESTART, VLCPlayer.DEFAULT_VLC_RESTART).equals("1")) { 272 quit(); 273 } 274 275 // 276 // Copy the new subtitle file to the VLC subtitle file 277 // 278 try { 279 if (subtitleFile != null) { 280 Utils.copyFile(new File(subtitleFile),vlcSubtitleFile); 281 } else { 282 // if no subtitle file exists then empty the subtitle file 283 OutputStream out = new FileOutputStream(vlcSubtitleFile); 284 out.close(); 285 } 286 } catch (IOException e) { 287 throw new PlayerException("Cannot copy the subtitle file"); 288 } 289 290 // 291 // launch VLC program and the TCP client 292 // 293 if (!isRunning()) { 294 // Clean all resources 295 quit(); 296 // Start VLC player 297 String vlcFile = SearsProperties.getProperty(SearsProperties.PLAYER_FULL_PATH); 298 try { 299 vlcProcess = Runtime.getRuntime().exec(vlcFile + " " + vlcParameter); 300 Thread.sleep(4000); 301 302 // Start the input stream thread 303 vlcInputStreamThread = new Thread("vlcInputStream") { 304 public void run() { 305 try { 306 BufferedReader is = new BufferedReader(new InputStreamReader(vlcProcess.getInputStream())); 307 while ((is.readLine()) != null) {} 308 } catch (IOException e) { 309 Trace.trace("VLC input stream thread failed:"+e.getMessage()); 310 } 311 } 312 }; 313 vlcInputStreamThread.start(); 314 315 // Start the error stream thread 316 vlcErrorStreamThread = new Thread("vlcErrorStream") { 317 public void run() { 318 try { 319 BufferedReader is = new BufferedReader(new InputStreamReader(vlcProcess.getErrorStream())); 320 while ((is.readLine()) != null) {} 321 } catch (IOException e) { 322 Trace.trace("VLC error stream thread failed:"+e.getMessage()); 323 } 324 } 325 }; 326 vlcErrorStreamThread.start(); 327 } catch (Exception e) { 328 quit(); 329 throw new PlayerException("Cannot run the VLC program:"+vlcFile); 330 } 331 } 332 333 // 334 // Load video file 335 // 336 if((currentVideoFile == null) || (currentVideoFile != videoFile)) { 337 sendRequest(EMPTY_PLAYLIST_REQUEST, false, false); 338 sendRequest(ADD_PLAYLIST_REQUEST+videoFile.replaceAll(" ","+"), false, false); 339 currentVideoFile = videoFile; 340 } else { 341 sendRequest(REPLAY_REQUEST, false, false); 342 } 343 344 // 345 // Restore the video position 346 // 347 if ((videoPosition != -1) && (videoPosition != 0)) { 348 videoPosition -= 5; // reduce video position 349 if(videoPosition < 0) 350 videoPosition = 0; 351 setPosition(videoPosition); 352 } 353 354 // 355 // Play video file 356 // 357 sendRequest(PLAY_REQUEST, false, false); 358 } 359 } 360 361 /** 362 * Empty method 363 * @param offset the offset 364 * @throws PlayerException if an error occurs 365 */ 366 public void goToOffset(int offset) throws PlayerException { 367 // TODO Auto-generated method stub 368 } 369 370 /* 371 * (non-Javadoc) 372 * 373 * @see sears.tools.player.PlayerInterface#quit() 374 */ 375 public void quit() { 376 // reset video file name 377 currentVideoFile = null; 378 379 // Quit VLC player 380 if (vlcProcess != null) { 381 try { 382 vlcProcess.exitValue(); 383 } catch (IllegalThreadStateException e) { 384 sendRequest(QUIT_REQUEST, true, false); 385 } 386 } 387 388 // Stop VLC input stream thread 389 if(vlcInputStreamThread != null) { 390 vlcInputStreamThread.interrupt(); 391 vlcInputStreamThread = null; 392 } 393 394 // Stop VLC error stream thread 395 if(vlcErrorStreamThread != null) { 396 vlcErrorStreamThread.interrupt(); 397 vlcErrorStreamThread = null; 398 } 399 400 // Stop VLC process 401 if (vlcProcess != null) { 402 try { 403 vlcProcess.destroy(); 404 } catch (Exception e) {} 405 vlcProcess = null; 406 } 407 } 408 409 /* (non-Javadoc) 410 * @see sears.tools.player.PlayerInterface#getPosition() 411 */ 412 public int getPosition() throws PlayerException { 413 int timeInSecond = -1; 414 if (isRunning()) { 415 String response = sendRequest(GET_TIME_REQUEST, true, true); 416 timeInSecond = Integer.valueOf(response).intValue(); 417 } 418 return timeInSecond; 419 } 420 421 /* (non-Javadoc) 422 * @see sears.tools.player.PlayerInterface#getPosition() 423 */ 424 public int getLength() throws PlayerException { 425 int timeInSecond = -1; 426 if (isRunning()) { 427 String response = sendRequest(GET_LENGTH_REQUEST, true, true); 428 timeInSecond = Integer.valueOf(response).intValue(); 429 } 430 return timeInSecond; 431 } 432 433 /* (non-Javadoc) 434 * @see sears.tools.player.PlayerInterface#pause() 435 */ 436 public void pause() throws PlayerException { 437 if (isRunning()) { 438 sendRequest(PAUSE_REQUEST, true, false); 439 } 440 } 441 442 /* (non-Javadoc) 443 * @see sears.tools.player.PlayerInterface#pause() 444 */ 445 public void stop() throws PlayerException { 446 if (isRunning()) { 447 sendRequest(STOP_REQUEST, true, false); 448 } 449 } 450 451 /* (non-Javadoc) 452 * @see sears.tools.player.PlayerInterface#setPosition(int) 453 */ 454 public void setPosition(int offset) throws PlayerException { 455 if (isRunning()) { 456 sendRequest(SEEK_REQUEST+Integer.toString(offset), true, false); 457 } 458 } 459 } 460 461 462