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