package org.jcodec.player.app; import static java.lang.Math.round; import static org.jcodec.common.IOUtils.forceMkdir; import java.awt.BorderLayout; import java.awt.Color; import java.io.File; import java.io.IOException; import java.net.URL; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.ArrayList; import java.util.List; import java.util.Timer; import java.util.TimerTask; import javax.swing.JApplet; import netscape.javascript.JSObject; import org.jcodec.common.IOUtils; import org.jcodec.common.StringUtils; import org.jcodec.common.model.ChannelLabel; import org.jcodec.common.model.RationalLarge; import org.jcodec.common.model.TapeTimecode; import org.jcodec.common.tools.Debug; import org.jcodec.player.Player; import org.jcodec.player.Player.Listener; import org.jcodec.player.Player.Status; import org.jcodec.player.Stepper; import org.jcodec.player.filters.JCodecVideoSource; import org.jcodec.player.filters.JSoundAudioOut; import org.jcodec.player.filters.MediaInfo; import org.jcodec.player.filters.MediaInfo.AudioInfo; import org.jcodec.player.filters.MediaInfo.VideoInfo; import org.jcodec.player.filters.audio.AudioMixer; import org.jcodec.player.filters.audio.AudioMixer.Pin; import org.jcodec.player.filters.audio.AudioSource; import org.jcodec.player.filters.audio.JCodecAudioSource; import org.jcodec.player.filters.http.HttpMedia; import org.jcodec.player.filters.http.HttpPacketSource; import org.jcodec.player.ui.SwingVO; /** * This class is part of JCodec ( www.jcodec.org ) This software is distributed * under FreeBSD License * * Media player applet for JCodec streaming * * @author The JCodec project * */ public class PlayerApplet extends JApplet { /** * */ private static final long serialVersionUID = -6134123851724553559L; private Player player; private JCodecVideoSource video; private SwingVO vo; public JSObject onStatus1; public JSObject onStatus2; public JSObject onStateChanged; private Timer timer = new Timer(true); private AudioMixer audio; private TimerTask status2Task; private Stepper stepper; public class Status1 { public double time; public int frame; public int[] tapeTimecode; public Status1(double time, int frame, int[] tapeTimecode) { this.time = time; this.frame = frame; this.tapeTimecode = tapeTimecode; } }; public class Status2 { public double duration; public int nFrames; public int[][] cache; public int[][] audio; public Status2(double duration, int nFrames, int[][] cache, int[][] audio) { this.duration = duration; this.nFrames = nFrames; this.cache = cache; this.audio = audio; } }; public void open(final String src) { Debug.println("Opening source: " + src); try { if (player != null) { Debug.println("Destroying old player."); player.destroy(); video.close(); audio.close(); status2Task.cancel(); status2Task = null; } HttpMedia http = AccessController.doPrivileged(new PrivilegedAction<HttpMedia>() { public HttpMedia run() { try { File cacheWhere = determineCacheLocation(); forceMkdir(cacheWhere); return new HttpMedia(new URL(src), cacheWhere); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException("Could not open HTTP source: '" + src + "'"); } } }); Debug.println("Initialized packet source"); Debug.println("Creating player"); final HttpPacketSource videoTrack = http.getVideoTrack(); video = new JCodecVideoSource(videoTrack); audio = audio(http); player = new Player(video, audio, vo, new JSoundAudioOut()); player.addListener(new Listener() { public void timeChanged(RationalLarge pts, int frameNo, TapeTimecode tt) { if (onStatus1 == null) return; onStatus1.call( "call", new Object[] { null, new Status1((double) pts.getNum() / pts.getDen(), frameNo, tt != null ? new int[] { tt.getHour(), tt.getMinute(), tt.getSecond(), tt.getFrame(), tt.isDropFrame() ? 1 : 0 } : null) }); } public void statusChanged(Status status) { if (onStateChanged != null) onStateChanged.call("call", new Object[] { null, status.toString() }); } }); status2Task = new TimerTask() { public void run() { if (onStatus2 == null) return; try { VideoInfo videoInfo = player.getVideoSource().getMediaInfo(); onStatus2.call("call", new Object[] { null, new Status2((double) videoInfo.getDuration() / videoInfo.getTimescale(), (int) videoInfo.getNFrames(), videoTrack.getCached(), enabledChannels()) }); } catch (IOException e) { e.printStackTrace(); } } }; timer.scheduleAtFixedRate(status2Task, 0, 1000); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException("Could not open source", e); } } private AudioMixer audio(HttpMedia http) throws IOException { List<HttpPacketSource> audioTracks = http.getAudioTracks(); AudioSource[] audios = new AudioSource[audioTracks.size()]; for (int i = 0; i < audioTracks.size(); i++) { audios[i] = new JCodecAudioSource(audioTracks.get(i)); } return new AudioMixer(2, audios); } public class AudioTrack { public AudioTrack(String name, String[] channels) { this.name = name; this.channels = channels; } public String name; public String[] channels; } public AudioTrack[] getAudioConfig() { try { Pin[] pins = audio.getPins(); AudioTrack[] ats = new AudioTrack[pins.length]; for (int i = 0; i < pins.length; i++) { AudioInfo ai = pins[i].getSource().getAudioInfo(); ats[i] = new AudioTrack(ai.getName(), channels(ai.getLabels())); } return ats; } catch (IOException e) { e.printStackTrace(); } return new AudioTrack[0]; } private String[] channels(ChannelLabel[] labels) { String[] strs = new String[labels.length]; for (int i = 0; i < labels.length; i++) { strs[i] = labels[i] != null ? StringUtils.capitaliseAllWords(labels[i].toString().toLowerCase().replace("_", " ")) : "N/A"; } return strs; } public void toggleChannel(int trackId, int channelId) { audio.getPins()[trackId].toggle(channelId); } public void muteChannel(int trackId, int channelId) { audio.getPins()[trackId].mute(channelId); } public void unmuteChannel(int trackId, int channelId) { audio.getPins()[trackId].unmute(channelId); } private int[][] enabledChannels() { Pin[] pins = audio.getPins(); int[][] result = new int[pins.length][]; for (int i = 0; i < pins.length; i++) { result[i] = pins[i].getSoloChannels(); } return result; } @Override public void init() { this.setBackground(Color.BLACK); vo = new SwingVO(); add(vo, BorderLayout.CENTER); validate(); String src = this.getParameter("src"); if (src != null) { open(src); if ("true".equalsIgnoreCase(this.getParameter("play"))) { player.play(); } } } @Override public void stop() { Debug.println("Stopping player"); if (player == null) return; player.destroy(); try { video.close(); audio.close(); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException("Could not destroy player", e); } } public void play() { if (player == null) throw new IllegalArgumentException("player is not initialized"); Debug.println("Starting playback."); if (stepper != null) { player.seek(stepper.getPos()); stepper = null; } player.play(); } public void pause() { if (player == null) throw new IllegalArgumentException("player is not initialized"); player.pause(); } public void togglePause() { if (player == null) throw new IllegalArgumentException("player is not initialized"); if (player.getStatus() == Player.Status.PAUSED) { if (stepper != null) { player.seek(stepper.getPos()); stepper = null; } player.play(); } else player.pause(); } public void seekRel(int sec) { if (player == null) throw new IllegalArgumentException("player is not initialized"); RationalLarge pos = player.getPos(); try { player.seek(new RationalLarge(pos.getNum() + pos.getDen() * sec, pos.getDen())); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException("Could not relative seek", e); } } public void seek(double sec) { if (player == null) throw new IllegalArgumentException("player is not initialized"); try { player.seek(new RationalLarge(round(sec * 1000000), 1000000)); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException("Could not seek", e); } } public void stepForward() { if (player.getStatus() != Player.Status.PAUSED) { player.pause(); return; } try { if (stepper == null) { stepper = new Stepper(video, audio, vo, new JSoundAudioOut()); stepper.setListeners(player.getListeners()); stepper.gotoFrame(player.getFrameNo()); } stepper.next(); } catch (Exception e) { throw new RuntimeException("Could not step", e); } } public void stepBackward() { // if (player.getStatus() != Player.Status.PAUSED) { // player.pause(); // return; // } // try { // if (stepper == null) { // stepper = new Stepper(video, audio, vo, new JSoundAudioOut()); // stepper.setListeners(player.getListeners()); // stepper.gotoFrame(player.getFrameNo()); // } // stepper.prev(); // } catch (Exception e) { // throw new RuntimeException("Could not step", e); // } } private File determineCacheLocation() { String os = System.getProperty("os.name"); String home = System.getProperty("user.home"); if (os.startsWith("Mac")) { return new File(home, "Library/JCodec"); } else if (os.startsWith("Windows")) { return new File(home, "Application Data/JCodec"); } else { return new File(home, ".jcodec"); } } public double getTime() { RationalLarge t = player.getPos(); return ((double) t.getNum()) / t.getDen(); } public MediaInfo[] getMediaInfo() throws IOException { ArrayList<MediaInfo> info = new ArrayList<MediaInfo>(); info.add(sourceInfo(video.getMediaInfo())); Pin[] pins = audio.getPins(); for (Pin pin : pins) { info.add(sourceInfo(pin.getSource().getAudioInfo())); } return info.toArray(new MediaInfo[0]); } private MediaInfo sourceInfo(MediaInfo mediaInfo) { MediaInfo result = mediaInfo; while (result.getTranscodedFrom() != null) result = result.getTranscodedFrom(); return result; } }