/**
 * Copyright (c) 2008 - 2014 Smaxe Ltd (www.smaxe.com).
 * All rights reserved.
 */

import com.smaxe.logger.ILogger;
import com.smaxe.logger.support.Loggers;
import com.smaxe.uv.client.rtsp.IMessageInterceptor;
import com.smaxe.uv.client.rtsp.IPresentation;
import com.smaxe.uv.client.rtsp.IVideo;
import com.smaxe.uv.client.rtsp.RtspClient;
import com.smaxe.uv.client.rtsp.RtspPlaySession;
import com.smaxe.uv.client.rtsp.RtspRequest;
import com.smaxe.uv.client.rtsp.RtspResponse;
import com.smaxe.uv.client.rtsp.video.AbstractVideo;
import com.smaxe.uv.client.rtsp.video.DispatcherVideo;
import com.smaxe.uv.media.core.IVideoDecoder;
import com.smaxe.uv.media.core.MediaTrackInfo;
import com.smaxe.uv.media.core.VideoFrame;
import com.smaxe.uv.media.java.swing.JVideoScreen;
import com.smaxe.uv.media.sdk.IMediaSdk;
import com.smaxe.uv.media.sdk.MediaSdkFactory;
import com.smaxe.uv.stream.MediaData;
import com.smaxe.uv.stream.MediaDataFactory;

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;

import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;

/**
 * <code>ExMediaSdkPlayRealtimeVideoOnlyRtspStream</code> - {@link IMediaSdk} example
 * that plays real-time video-only RTSP/RTP stream.
 * <p> Note:
 * 
 * @author Andrei Sochirca
 * @see <a href="http://www.smaxe.com/product.jsf?id=juv-media-sdk" target="_blank">JUV Media SDK</a>
 * @see <a href="http://www.smaxe.com/product.jsf?id=juv-rtsp-client" target="_blank">JUV RTSP/RTP Client</a>
 */
public final class ExMediaSdkPlayRealtimeVideoOnlyRtspStream extends Object
{
    /**
     * Entry point.
     * 
     * @param args
     * @throws Exception
     */
    public static void main(final String[] args) throws Exception
    {
        // NOTE:
        // Evaluation version has some limitations:
        // - video decoder watermarks frames (and blacks it sometimes)
        
        final String url = args.length == 0 ? "rtsp://184.72.239.149/vod/mp4:BigBuckBunny_175k.mov" : args[0];
        
        final IMediaSdk mediaSdk = MediaSdkFactory.getMediaSdk();
        
        final JVideoScreen videoScreen = new JVideoScreen();
        
        final JButton start = new JButton(new AbstractAction("Play")
        {
            private final static long serialVersionUID = -3538347901572402162L;
            
            public void actionPerformed(final ActionEvent e)
            {
                new Thread(new Runnable()
                {
                    public void run()
                    {
                        final RtspPlayer player = new RtspPlayer();
                        
                        player.play(url, new DispatcherVideo(new Video(mediaSdk, videoScreen)));
                    }
                }, "ExMediaSdkPlayRealtimeVideoOnlyRtspStream-Player").start();
            }
        });
        
        final JFrame frame = new JFrame();
        
        frame.getContentPane().setLayout(new BorderLayout());
        frame.getContentPane().add(videoScreen, BorderLayout.CENTER);
        frame.getContentPane().add(start, BorderLayout.SOUTH);
        frame.pack();
        
        frame.setTitle(url);
        frame.setVisible(true);
    }
    
    /**
     * <code>RtspPlayer</code> - RTSP Player.
     */
    public final static class RtspPlayer extends Object
    {
        /**
         * <code>MessageInterceptor</code> - {@link IMessageInterceptor} implementation.
         */
        private final class MessageInterceptor extends Object implements IMessageInterceptor
        {
            /**
             * Constructor.
             */
            public MessageInterceptor()
            {
            }
            
            // IMessageInterceptor implementation
            
            public RtspRequest onRequest(final String source, final RtspRequest request)
            {
                logger.log(ILogger.DEBUG, "onRequest", null /*t*/, source, request);
                
                return request;
            }
            
            public RtspResponse onResponse(final String source, final RtspResponse response)
            {
                logger.log(ILogger.DEBUG, "onResponse", null /*t*/, source, response);
                
                return response;
            }
        }
        
        // fields
        // beans
        private ILogger logger = Loggers.createSoLogger(ILogger.DEBUG, "Logger");
        
        // state
        private IPresentation presentation = null;
        private volatile boolean disconnected = false;
        private volatile String disconnectionReason = null;
        
        /**
         * Constructor.
         */
        public RtspPlayer()
        {
        }
        
        /**
         * Sets logger.
         * 
         * @param logger
         */
        public void setLogger(final ILogger logger)
        {
            this.logger = logger == null ? Loggers.createNullLogger() : logger;
        }
        
        /**
         * Returns playing presentation.
         * 
         * @return playing presentation
         */
        public IPresentation getPresentation()
        {
            return presentation;
        }
        
        /**
         * Checks if playing.
         * 
         * @return <code>true</code> if playing; otherwise <code>false</code>
         */
        public boolean isPlaying()
        {
            return !disconnected;
        }
        
        /**
         * Plays the stream.
         * <p> Note:
         * <br> - The method blocks until the player connection is closed, so run it
         * in a separate thread.
         * 
         * @param url rtmp url
         * @param video video
         * @param udpPort UDP port to use
         */
        public void play(final String url, final IVideo video)
        {
            final RtspClient client = new RtspClient();
            
            try
            {
                client.configuration().put(RtspClient.Configuration.DATAGRAM_JITTER_BUFFER_MARGIN, 16 /*packets*/);
                client.configuration().put(RtspClient.Configuration.DATAGRAM_SO_ENABLE_CONFIGURATION, true);
                client.configuration().put(RtspClient.Configuration.DATAGRAM_SO_RCVBUF, 64 * 1024);
                
                client.configuration().put(RtspClient.Configuration.MESSAGE_INTERCEPTOR, new MessageInterceptor());
                
                client.connect(url);
                
                if (presentation == null)
                {
                    presentation = client.describe(url);
                    
                    System.out.println("Presentation: " + presentation);
                }
                
                presentation = RtspClient.createPartOfPresentationVideoOnly(presentation);
                
                final RtspPlaySession session = client.createPlaySession(presentation);
                
                if (!RtspClient.isOK(session.setup(22222)))
                {
                    session.setup();
                }
                
                session.play(video);
                
                while (!disconnected)
                {
                    try
                    {
                        Thread.sleep(100);
                    }
                    catch (Exception e) {/*ignore*/}
                }
                
                session.teardown();
            }
            catch (Exception e)
            {
                logger.log(ILogger.ERROR, "RTSP/RTP exception", e);
            }
            finally
            {
                client.close(disconnectionReason);
            }
        }
        
        /**
         * Stops stream playback.
         * 
         * @param reason
         */
        public void stop(final String reason)
        {
            disconnectionReason = reason;
            disconnected = true;
        }
    }    
    
    /**
     * <code>Video</code>
     */
    private final static class Video extends AbstractVideo implements IVideo
    {
        // fields
        private final IMediaSdk mediaSdk;
        private final JVideoScreen videoScreen;
        
        // state
        private IVideoDecoder videoDecoder;
        
        /**
         * Constructor.
         * 
         * @param mediaSdk
         * @param videoScreen
         */
        public Video(final IMediaSdk mediaSdk, final JVideoScreen videoScreen)
        {
            this.mediaSdk = mediaSdk;
            this.videoScreen = videoScreen;
        }
        
        // IVideo implementation
        
        @Override
        public void onMediaData(final MediaTrackInfo track, final MediaData data)
        {
            System.out.println("onMediaData: " + track + "  " + data);
            
            if (track.type != MediaTrackInfo.VIDEO) return;
            
            if (videoDecoder == null)
            {
                videoDecoder = mediaSdk.getMediaCodecSdk().createVideoDecoder(track.codec, track.codecConfig, null);
            }
            
            videoDecoder.decodeFrame(data.timestamp, MediaDataFactory.getMediaDataPayload(data), new IVideoDecoder.CallbackAdapter()
            {
                @Override
                public void onDecodedFrame(final VideoFrame frame)
                {
                    SwingUtilities.invokeLater(new Runnable()
                    {
                        public void run()
                        {
                            videoScreen.setFrame(frame);
                        }
                    });
                }
                
                @Override
                public void onException(final Object tag, final Exception e)
                {
                    e.printStackTrace();
                }
            });
        }
    }
    
    /**
     * Constructor.
     */
    private ExMediaSdkPlayRealtimeVideoOnlyRtspStream()
    {
    }
}