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();
}
}