All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.libav.DefaultMediaPlayer Maven / Gradle / Ivy

There is a newer version: 0.2.1
Show newest version
/*
 * Copyright (C) 2012 Ondrej Perutka
 *
 * This program is free software: you can redistribute it and/or 
 * modify it under the terms of the GNU Lesser General Public 
 * License as published by the Free Software Foundation, either 
 * version 3 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public 
 * License along with this library. If not, see 
 * .
 */
package org.libav;

import java.util.logging.Level;
import java.util.logging.Logger;
import org.libav.audio.AudioFrameDecoder;
import org.libav.avcodec.IFrameWrapper;
import org.libav.avformat.IFormatContextWrapper;
import org.libav.avformat.IStreamWrapper;
import org.libav.data.IPacketConsumer;
import org.libav.video.VideoFrameDecoder;

/**
 * Default implementation of the media player interface.
 * 
 * @author Ondrej Perutka
 */
public class DefaultMediaPlayer implements IMediaPlayer {

    private IMediaReader mr;
    private boolean liveStream;
    
    private IDecoder[] aDecoders;
    private IDecoder[] vDecoders;
    
    private ThreadedStreamPlayer[] streamPlayers;
    private Thread[] playerThreads;
    private long stopPosition;
    private long startTime;

    /**
     * Create a new media player and open the given media URL using the default
     * media decoder.
     * 
     * @param url a media URL
     * @throws LibavException if an error occurs while opening or player 
     * initialization
     */
    public DefaultMediaPlayer(String url) throws LibavException {
        this(url, isUrlLiveStream(url));
    }
    
    /**
     * Create a new media player, open the given media URL using the default
     * media decoder and set the live stream flag.
     * 
     * @param url a media URL
     * @param liveStream live stream flag; it changes time synchronization 
     * settings to satisfy live stream needs
     * @throws LibavException if an error occurs while opening or player 
     * initialization
     */
    public DefaultMediaPlayer(String url, boolean liveStream) throws LibavException {
        this(new DefaultMediaReader(url), liveStream);
    }
    
    protected DefaultMediaPlayer(IMediaReader mr, boolean liveStream) {
        this.mr = new MediaReaderAdapter(mr);
        this.liveStream = liveStream;
        
        aDecoders = new IDecoder[mr.getAudioStreamCount()];
        vDecoders = new IDecoder[mr.getVideoStreamCount()];
        
        playerThreads = null;
        streamPlayers = null;
        stopPosition = 0;
        startTime = 0;
    }
    
    @Override
    public IMediaReader getMediaReader() {
        return mr;
    }

    @Override
    public synchronized void setVideoStreamDecodingEnabled(int videoStreamIndex, boolean enabled) throws LibavException {
        if (enabled) {
            mr.addVideoPacketConsumer(videoStreamIndex, getVideoStreamDecoder(videoStreamIndex));
            mr.setVideoStreamBufferingEnabled(videoStreamIndex, true);
            startVideoStreamPlayback(videoStreamIndex);
        } else {
            mr.removeVideoPacketConsumer(videoStreamIndex, getVideoStreamDecoder(videoStreamIndex));
            mr.setVideoStreamBufferingEnabled(videoStreamIndex, false);
            stopStreamPlayback(mr.getVideoStream(videoStreamIndex).getIndex());
        }
    }

    @Override
    public boolean isVideoStreamDecodingEnabled(int videoStreamIndex) throws LibavException {
        return mr.containsVideoPacketConsumer(videoStreamIndex, getVideoStreamDecoder(videoStreamIndex));
    }

    @Override
    public IDecoder getVideoStreamDecoder(int videoStreamIndex) throws LibavException {
        if (vDecoders[videoStreamIndex] == null)
            vDecoders[videoStreamIndex] = new SynchronizedVideoFrameDecoder(mr.getVideoStream(videoStreamIndex));
        
        return vDecoders[videoStreamIndex];
    }
    
    @Override
    public synchronized void setAudioStreamDecodingEnabled(int audioStreamIndex, boolean enabled) throws LibavException {
        if (enabled) {
            mr.addAudioPacketConsumer(audioStreamIndex, getAudioStreamDecoder(audioStreamIndex));
            mr.setAudioStreamBufferingEnabled(audioStreamIndex, true);
            startAudioStreamPlayback(audioStreamIndex);
        } else {
            mr.removeAudioPacketConsumer(audioStreamIndex, getAudioStreamDecoder(audioStreamIndex));
            mr.setAudioStreamBufferingEnabled(audioStreamIndex, false);
            stopStreamPlayback(mr.getAudioStream(audioStreamIndex).getIndex());
        }
    }

    @Override
    public boolean isAudioStreamDecodingEnabled(int audioStreamIndex) throws LibavException {
        return mr.containsAudioPacketConsumer(audioStreamIndex, getAudioStreamDecoder(audioStreamIndex));
    }

    @Override
    public IDecoder getAudioStreamDecoder(int audioStreamIndex) throws LibavException {
        if (aDecoders[audioStreamIndex] == null)
            aDecoders[audioStreamIndex] = new SynchronizedAudioFrameDecoder(mr.getAudioStream(audioStreamIndex));
        
        return aDecoders[audioStreamIndex];
    }
    
    private synchronized void startVideoStreamPlayback(int videoStreamIndex) {
        IStreamWrapper sw = mr.getVideoStream(videoStreamIndex);
        int si = sw.getIndex();
        
        if (streamPlayers == null || streamPlayers[si] != null)
            return;
        
        if (liveStream)
            stopPosition = System.currentTimeMillis() - startTime - 500;
        
        streamPlayers[si] = new ThreadedVideoStreamPlayer(mr, videoStreamIndex, stopPosition);
        playerThreads[si] = new Thread(streamPlayers[si], "StreamPlayer");
        playerThreads[si].setDaemon(true);
        playerThreads[si].start();
    }
    
    private synchronized void startAudioStreamPlayback(int audioStreamIndex) {
        IStreamWrapper sw = mr.getAudioStream(audioStreamIndex);
        int si = sw.getIndex();
        
        if (streamPlayers == null || streamPlayers[si] != null)
            return;
        
        if (liveStream)
            stopPosition = System.currentTimeMillis() - startTime - 500;
        
        streamPlayers[si] = new ThreadedAudioStreamPlayer(mr, audioStreamIndex, stopPosition);
        playerThreads[si] = new Thread(streamPlayers[si], "StreamPlayer");
        playerThreads[si].setDaemon(true);
        playerThreads[si].start();
    }
    
    private synchronized void stopStreamPlayback(int streamIndex) {
        streamPlayers[streamIndex].stop();
        try {
            playerThreads[streamIndex].join();
        } catch (InterruptedException ex) {
            Logger.getLogger(DefaultMediaPlayer.class.getName()).log(Level.WARNING, "interrupted while waiting for stream player to stop", ex);
        }
        streamPlayers[streamIndex] = null;
        playerThreads[streamIndex] = null;
    }

    @Override
    public synchronized void play() throws LibavException {
        if (streamPlayers != null)
            return;
        
        streamPlayers = new ThreadedStreamPlayer[mr.getStreamCount()];
        playerThreads = new Thread[streamPlayers.length];
        
        if (startTime == 0 || !liveStream)
            startTime = System.currentTimeMillis();
        if (liveStream) {
            stopPosition = System.currentTimeMillis() - startTime - 500; // give it a time to work with network delay
            mr.dropAllBuffers();
        }
        
        IStreamWrapper sw;
        for (int i = 0; i < mr.getVideoStreamCount(); i++) {
            if (!isVideoStreamDecodingEnabled(i))
                continue;
            mr.setVideoStreamBufferingEnabled(i, true);
            sw = mr.getVideoStream(i);
            streamPlayers[sw.getIndex()] = new ThreadedVideoStreamPlayer(mr, i, stopPosition);
            playerThreads[sw.getIndex()] = new Thread(streamPlayers[sw.getIndex()], "StreamPlayer");
            playerThreads[sw.getIndex()].setDaemon(true);
        }
        
        for (int i = 0; i < mr.getAudioStreamCount(); i++) {
            if (!isAudioStreamDecodingEnabled(i))
                continue;
            mr.setAudioStreamBufferingEnabled(i, true);
            sw = mr.getAudioStream(i);
            streamPlayers[sw.getIndex()] = new ThreadedAudioStreamPlayer(mr, i, stopPosition);
            playerThreads[sw.getIndex()] = new Thread(streamPlayers[sw.getIndex()], "StreamPlayer");
            playerThreads[sw.getIndex()].setDaemon(true);
        }
        
        for (int i = 0; i < playerThreads.length; i++) {
            if (playerThreads[i] != null)
                playerThreads[i].start();
        }
    }

    @Override
    public synchronized void stop() {
        if (streamPlayers == null)
            return;
        
        for (ThreadedStreamPlayer sp : streamPlayers) {
            if (sp != null)
                sp.stop();
        }
        
        try {
            join();
        } catch (InterruptedException ex) {
            Logger.getLogger(DefaultMediaPlayer.class.getName()).log(Level.WARNING, "interrupted while waiting for playback to stop", ex);
        }
        
        stopPosition += System.currentTimeMillis() - startTime;
        
        streamPlayers = null;
    }

    @Override
    public void join() throws InterruptedException {
        Thread[] pts = playerThreads;
        if (pts == null)
            return;
        
        for (Thread pt : pts) {
            if (pt != null)
                pt.join();
        }
    }

    @Override
    public synchronized void close() throws LibavException {
        stop();
        
        for (IDecoder afd : aDecoders) {
            if (afd != null)
                afd.close();
        }
        
        for (IDecoder vfd : vDecoders) {
            if (vfd != null)
                vfd.close();
        }
        
        mr.close();
    }
    
    private static boolean isUrlLiveStream(String url) {
        url = url.toLowerCase();
        if (url.startsWith("rtp:"))
            return true;
        if (url.startsWith("rtsp:"))
            return true;
        if (url.endsWith(".sdp"))
            return true;
        
        return false;
    }
    
    private class ThreadedVideoStreamPlayer extends ThreadedStreamPlayer {
        private long startPoint;
        private int videoStreamIndex;
        
        public ThreadedVideoStreamPlayer(IMediaReader mr, int videoStreamIndex, long startPoint) {
            super(mr, mr.getVideoStream(videoStreamIndex).getIndex());
            
            this.videoStreamIndex = videoStreamIndex;
            this.startPoint = startPoint;
        }

        @Override
        public void run() {
            IDecoder decoder = null;
            
            try {
                decoder = getVideoStreamDecoder(videoStreamIndex);
                if (decoder instanceof SynchronizedVideoFrameDecoder)
                    ((SynchronizedVideoFrameDecoder)decoder).setStartPoint(System.currentTimeMillis() - startPoint);
            } catch (LibavException ex) {
                Logger.getLogger(DefaultMediaPlayer.class.getName()).log(Level.SEVERE, "unable to get video frame decoder", ex);
            }
            
            super.run();
            
            try {
                if (decoder != null)
                    decoder.flush();
            } catch (LibavException ex) {
                Logger.getLogger(DefaultMediaPlayer.class.getName()).log(Level.SEVERE, "unable to flush video frame decoder", ex);
            }
        }
    }
    
    private class ThreadedAudioStreamPlayer extends ThreadedStreamPlayer {
        private long startPoint;
        private int audioStreamIndex;
        
        public ThreadedAudioStreamPlayer(IMediaReader mr, int audioStreamIndex, long startPoint) {
            super(mr, mr.getAudioStream(audioStreamIndex).getIndex());
            
            this.audioStreamIndex = audioStreamIndex;
            this.startPoint = startPoint;
        }

        @Override
        public void run() {
            IDecoder decoder = null;
            
            try {
                decoder = getAudioStreamDecoder(audioStreamIndex);
                if (decoder instanceof SynchronizedAudioFrameDecoder)
                    ((SynchronizedAudioFrameDecoder)decoder).setStartPoint(System.currentTimeMillis() - startPoint);
            } catch (LibavException ex) {
                Logger.getLogger(DefaultMediaPlayer.class.getName()).log(Level.SEVERE, "unable to get audio frame decoder", ex);
            }
            
            super.run();
            
            try {
                if (decoder != null)
                    decoder.flush();
            } catch (LibavException ex) {
                Logger.getLogger(DefaultMediaPlayer.class.getName()).log(Level.SEVERE, "unable to flush audio frame decoder", ex);
            }
        }
    }
    
    private static class ThreadedStreamPlayer implements Runnable {
        protected IMediaReader mr;
        private int streamIndex;
        private boolean stop;

        public ThreadedStreamPlayer(IMediaReader mr, int streamIndex) {
            this.mr = mr;
            this.streamIndex = streamIndex;
            
            stop = false;
        }
        
        @Override
        public void run() {
            while (!stop) {
                try {
                    if (!mr.readNextPacket(streamIndex))
                        stop = true;
                } catch (LibavException ex) {
                    Logger.getLogger(DefaultMediaPlayer.class.getName()).log(Level.SEVERE, "error while playing media", ex);
                }
            }
        }

        public void stop() {
            stop = true;
        }
    }
    
    private static class SynchronizedVideoFrameDecoder extends VideoFrameDecoder {
        private long startPoint;
        
        public SynchronizedVideoFrameDecoder(IStreamWrapper stream) throws LibavException {
            super(stream);
            
            startPoint = 0;
        }
        
        public void setStartPoint(long startPoint) {
            this.startPoint = startPoint;
        }

        @Override
        protected void sendFrame(IFrameWrapper frame) throws LibavException {
            long tmp = frame.getPts() + startPoint - System.currentTimeMillis();
            //System.out.printf("VF: pts = %d, tmp = %d (expected_pts = %d, start_point = %d)\n", frame.getPts(), tmp, System.currentTimeMillis() - startPoint, startPoint);
            if (tmp > 0) {
                try {
                    Thread.sleep(tmp);
                } catch (InterruptedException ex) {
                    Logger.getLogger(DefaultMediaPlayer.class.getName()).log(Level.WARNING, "interrupted while waiting for a sync point");
                }
            } else if (tmp < -10) // drop frame if it is too late
                return;
            
            super.sendFrame(frame);
        }
    }
    
    private static class SynchronizedAudioFrameDecoder extends AudioFrameDecoder {
        private long startPoint;
        
        public SynchronizedAudioFrameDecoder(IStreamWrapper stream) throws LibavException {
            super(stream);
            
            startPoint = 0;
        }
        
        public void setStartPoint(long startPoint) {
            this.startPoint = startPoint;
        }

        @Override
        protected void sendFrame(IFrameWrapper frame) throws LibavException {
            long tmp = frame.getPts() + startPoint - System.currentTimeMillis() - 500;
            //System.out.printf("AF: pts = %d, tmp = %d (expected_pts = %d, start_point = %d)\n", frame.getPts(), tmp, System.currentTimeMillis() - startPoint, startPoint);
            if (tmp > 0) {
                try {
                    Thread.sleep(tmp);
                } catch (InterruptedException ex) {
                    Logger.getLogger(DefaultMediaPlayer.class.getName()).log(Level.WARNING, "interrupted while waiting for a sync point");
                }
            } else if (tmp < -100) // drop frame if it is too late
                return;
            
            super.sendFrame(frame);
        }
    }
    
    private class MediaReaderAdapter implements IMediaReader {
        private IMediaReader mr;

        public MediaReaderAdapter(IMediaReader mr) {
            this.mr = mr;
        }
        
        @Override
        public IFormatContextWrapper getFormatContext() {
            return mr.getFormatContext();
        }

        @Override
        public int getStreamCount() {
            return mr.getStreamCount();
        }

        @Override
        public void addPacketConsumer(int streamIndex, IPacketConsumer consumer) {
            mr.addPacketConsumer(streamIndex, consumer);
        }

        @Override
        public void removePacketConsumer(int streamIndex, IPacketConsumer consumer) {
            mr.removePacketConsumer(streamIndex, consumer);
        }

        @Override
        public boolean containsPacketConsumer(int streamIndex, IPacketConsumer consumer) {
            return mr.containsPacketConsumer(streamIndex, consumer);
        }

        @Override
        public IStreamWrapper getStream(int streamIndex) {
            return mr.getStream(streamIndex);
        }

        @Override
        public int getVideoStreamCount() {
            return mr.getVideoStreamCount();
        }

        @Override
        public IStreamWrapper getVideoStream(int videoStreamIndex) {
            return mr.getVideoStream(videoStreamIndex);
        }

        @Override
        public void addVideoPacketConsumer(int videoStreamIndex, IPacketConsumer consumer) {
            mr.addVideoPacketConsumer(videoStreamIndex, consumer);
        }

        @Override
        public void removeVideoPacketConsumer(int videoStreamIndex, IPacketConsumer consumer) {
            mr.removeVideoPacketConsumer(videoStreamIndex, consumer);
        }

        @Override
        public boolean containsVideoPacketConsumer(int videoStreamIndex, IPacketConsumer consumer) {
            return mr.containsVideoPacketConsumer(videoStreamIndex, consumer);
        }

        @Override
        public int getAudioStreamCount() {
            return mr.getAudioStreamCount();
        }

        @Override
        public IStreamWrapper getAudioStream(int audioStremIndex) {
            return mr.getAudioStream(audioStremIndex);
        }

        @Override
        public void addAudioPacketConsumer(int audioStreamIndex, IPacketConsumer consumer) {
            mr.addAudioPacketConsumer(audioStreamIndex, consumer);
        }

        @Override
        public void removeAudioPacketConsumer(int audioStreamIndex, IPacketConsumer consumer) {
            mr.removeAudioPacketConsumer(audioStreamIndex, consumer);
        }

        @Override
        public boolean containsAudioPacketConsumer(int audioStreamIndex, IPacketConsumer consumer) {
            return mr.containsAudioPacketConsumer(audioStreamIndex, consumer);
        }

        @Override
        public long getDuration() {
            return mr.getDuration();
        }

        @Override
        public long getStreamDuration(int streamIndex) {
            return mr.getStreamDuration(streamIndex);
        }

        @Override
        public long getVideoStreamDuration(int videoStreamIndex) {
            return mr.getVideoStreamDuration(videoStreamIndex);
        }

        @Override
        public long getAudioStreamDuration(int audioStreamIndex) {
            return mr.getAudioStreamDuration(audioStreamIndex);
        }

        @Override
        public long getPosition() {
            return mr.getPosition();
        }

        @Override
        public boolean isSeekable() {
            return mr.isSeekable();
        }

        @Override
        public void seek(long time) throws LibavException {
            synchronized (DefaultMediaPlayer.this) {
                if (!isSeekable())
                    return;

                boolean replay = streamPlayers != null;
                stop();
                mr.seek(time);
                stopPosition = time;
                if (replay)
                    play();
            }
        }

        @Override
        public void dropAllBuffers() {
            mr.dropAllBuffers();
        }

        @Override
        public boolean readNextPacket() throws LibavException {
            return mr.readNextPacket();
        }

        @Override
        public boolean readNextPacket(int streamIndex) throws LibavException {
            return mr.readNextPacket(streamIndex);
        }

        @Override
        public boolean readNextAudioPacket(int audioStreamIndex) throws LibavException {
            return mr.readNextAudioPacket(audioStreamIndex);
        }

        @Override
        public boolean readNextVideoPacket(int videoStreamIndex) throws LibavException {
            return mr.readNextVideoPacket(videoStreamIndex);
        }

        @Override
        public void setStreamBufferingEnabled(int streamIndex, boolean enabled) {
            mr.setStreamBufferingEnabled(streamIndex, enabled);
        }

        @Override
        public boolean isStreamBufferingEnabled(int streamIndex) {
            return mr.isStreamBufferingEnabled(streamIndex);
        }

        @Override
        public void setVideoStreamBufferingEnabled(int videoStreamIndex, boolean enabled) {
            mr.setVideoStreamBufferingEnabled(videoStreamIndex, enabled);
        }

        @Override
        public boolean isVideoStreamBufferingEnabled(int videoStreamIndex) {
            return mr.isVideoStreamBufferingEnabled(videoStreamIndex);
        }

        @Override
        public void setAudioStreamBufferingEnabled(int audioStreamIndex, boolean enabled) {
            mr.setAudioStreamBufferingEnabled(audioStreamIndex, enabled);
        }

        @Override
        public boolean isAudioStreamBufferingEnabled(int audioStreamIndex) {
            return mr.isAudioStreamBufferingEnabled(audioStreamIndex);
        }

        @Override
        public void close() throws LibavException {
            mr.close();
        }

        @Override
        public boolean isClosed() {
            return mr.isClosed();
        }
    }
    
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy