package com.googlecode.mp4parser; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.AbstractHandler; import org.mp4parser.Container; import org.mp4parser.muxer.Movie; import org.mp4parser.muxer.Track; import org.mp4parser.muxer.builder.DefaultMp4Builder; import org.mp4parser.muxer.container.mp4.MovieCreator; import org.mp4parser.muxer.tracks.ClippedTrack; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.nio.channels.Channels; import java.nio.channels.WritableByteChannel; import java.util.Arrays; import java.util.LinkedList; import java.util.List; /** * Created with IntelliJ IDEA. * User: sannies * Date: 10/28/12 * Time: 11:08 AM * To change this template use File | Settings | File Templates. */ public class ServeMp4 extends AbstractHandler { Movie movie; public ServeMp4(Movie movie) { this.movie = movie; } public static void main(String[] args) throws Exception { Movie movie = MovieCreator.build("/home/sannies/CSI.S13E02.HDTV.x264-LOL.mp4"); Server server = new Server(8080); server.setHandler(new ServeMp4(movie)); server.start(); server.join(); } private static double correctTimeToSyncSample(Track track, double cutHere, boolean next) { double[] timeOfSyncSamples = new double[track.getSyncSamples().length]; long currentSample = 0; double currentTime = 0; for (int i = 0; i < track.getSampleDurations().length; i++) { long delta = track.getSampleDurations()[i]; if (Arrays.binarySearch(track.getSyncSamples(), currentSample + 1) >= 0) { // samples always start with 1 but we start with zero therefore +1 timeOfSyncSamples[Arrays.binarySearch(track.getSyncSamples(), currentSample + 1)] = currentTime; } currentTime += (double) delta / (double) track.getTrackMetaData().getTimescale(); currentSample++; } double previous = 0; for (double timeOfSyncSample : timeOfSyncSamples) { if (timeOfSyncSample > cutHere) { if (next) { return timeOfSyncSample; } else { return previous; } } previous = timeOfSyncSample; } return timeOfSyncSamples[timeOfSyncSamples.length - 1]; } public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { String start = request.getParameter("start"); List<Track> tracks = movie.getTracks(); movie.setTracks(new LinkedList<Track>()); // remove all tracks we will create new tracks from the old double startTime = Double.parseDouble(start); double endTime = (double) tracks.get(0).getDuration() / tracks.get(0).getTrackMetaData().getTimescale(); boolean timeCorrected = false; // Here we try to find a track that has sync samples. Since we can only start decoding // at such a sample we SHOULD make sure that the start of the new fragment is exactly // such a frame for (Track track : tracks) { if (track.getSyncSamples() != null && track.getSyncSamples().length > 0) { if (timeCorrected) { // This exception here could be a false positive in case we have multiple tracks // with sync samples at exactly the same positions. E.g. a single movie containing // multiple qualities of the same video (Microsoft Smooth Streaming file) throw new RuntimeException("The startTime has already been corrected by another track with SyncSample. Not Supported."); } startTime = correctTimeToSyncSample(track, startTime, false); endTime = correctTimeToSyncSample(track, endTime, true); timeCorrected = true; } } for (Track track : tracks) { long currentSample = 0; double currentTime = 0; long startSample = -1; long endSample = -1; for (int i = 0; i < track.getSampleDurations().length; i++) { long delta = track.getSampleDurations()[i]; if (currentTime <= startTime) { // current sample is still before the new starttime startSample = currentSample; } if (currentTime <= endTime) { // current sample is after the new start time and still before the new endtime endSample = currentSample; } else { // current sample is after the end of the cropped video break; } currentTime += (double) delta / (double) track.getTrackMetaData().getTimescale(); currentSample++; } movie.addTrack(new ClippedTrack(track, startSample, endSample)); } Container out = new DefaultMp4Builder().build(movie); response.setHeader("content-type", "video/mp4"); WritableByteChannel reponse = Channels.newChannel(response.getOutputStream()); out.writeContainer(reponse); reponse.close(); } }