Adapted from the byte range request servlet code from the The BalusC Code, here is a Java 7 pseudostreaming servlet for playing video using the HTML5 video tag.
The servlet takes the name of a video file to play as a request parameter and the byte range of the video to stream in the Range header field.
PseudostreamingServlet.java
package org.adrianwalker.pseudostreaming;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URLDecoder;
import java.nio.ByteBuffer;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import static java.nio.file.StandardOpenOption.READ;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public final class PseudostreamingServlet extends HttpServlet {
private static final int BUFFER_LENGTH = 1024 * 16;
private static final long EXPIRE_TIME = 1000 * 60 * 60 * 24;
private static final Pattern RANGE_PATTERN = Pattern.compile("bytes=(?\\d*)-(?\\d*)");
private String videoPath;
@Override
public void init() throws ServletException {
videoPath = getInitParameter("videoPath");
}
@Override
protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException {
processRequest(request, response);
}
private void processRequest(final HttpServletRequest request, final HttpServletResponse response) throws IOException {
String videoFilename = URLDecoder.decode(request.getParameter("video"), "UTF-8");
Path video = Paths.get(videoPath, videoFilename);
int length = (int) Files.size(video);
int start = 0;
int end = length - 1;
String range = request.getHeader("Range");
Matcher matcher = RANGE_PATTERN.matcher(range);
if (matcher.matches()) {
String startGroup = matcher.group("start");
start = startGroup.isEmpty() ? start : Integer.valueOf(startGroup);
start = start < 0 ? 0 : start;
String endGroup = matcher.group("end");
end = endGroup.isEmpty() ? end : Integer.valueOf(endGroup);
end = end > length - 1 ? length - 1 : end;
}
int contentLength = end - start + 1;
response.reset();
response.setBufferSize(BUFFER_LENGTH);
response.setHeader("Content-Disposition", String.format("inline;filename=\"%s\"", videoFilename));
response.setHeader("Accept-Ranges", "bytes");
response.setDateHeader("Last-Modified", Files.getLastModifiedTime(video).toMillis());
response.setDateHeader("Expires", System.currentTimeMillis() + EXPIRE_TIME);
response.setContentType(Files.probeContentType(video));
response.setHeader("Content-Range", String.format("bytes %s-%s/%s", start, end, length));
response.setHeader("Content-Length", String.format("%s", contentLength));
response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
int bytesRead;
int bytesLeft = contentLength;
ByteBuffer buffer = ByteBuffer.allocate(BUFFER_LENGTH);
try (SeekableByteChannel input = Files.newByteChannel(video, READ);
OutputStream output = response.getOutputStream()) {
input.position(start);
while ((bytesRead = input.read(buffer)) != -1 && bytesLeft > 0) {
buffer.clear();
output.write(buffer.array(), 0, bytesLeft < bytesRead ? bytesLeft : bytesRead);
bytesLeft -= bytesRead;
}
}
}
}
The location of the videos on the file system is configurable in the web.xml via the videoPath parameter for the stream servlet.
web.xml
stream
org.adrianwalker.pseudostreaming.PseudostreamingServlet
videoPath
/tmp/videos
stream
/stream
30
Some simple HTML5 uses the servlet as the source for the video tag. The name of the video files is provided by the video URL parameter.
index.html
HTML5 Video PseudostreamingYou're going to need a video to play, so download a trailer or something:
The QuickTime trailer video file isn't supported by my target browser, so I need to convert it to another format, for this example WebM. ffmpeg can be used for the conversion:
ffmpeg -i amazingspiderman-tlr2b-USA_h480p.mov /tmp/videos/video.webm
Source Code
Pseudo-streaming Maven Project - pseudostreaming.zip
Usage
Run the project with 'mvn clean install jetty:run-war' and point your brower at http://localhost:8080/pseudostreaming.