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