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=(?<start>\\d*)-(?<end>\\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
<?xml version="1.0" encoding="UTF-8"?> <web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"> <servlet> <servlet-name>stream</servlet-name> <servlet-class>org.adrianwalker.pseudostreaming.PseudostreamingServlet</servlet-class> <init-param> <param-name>videoPath</param-name> <param-value>/tmp/videos</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>stream</servlet-name> <url-pattern>/stream</url-pattern> </servlet-mapping> <session-config> <session-timeout> 30 </session-timeout> </session-config> </web-app>
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
<!DOCTYPE html> <html> <head> <title>HTML5 Video Pseudostreaming</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> </head> <body> <div> <video id="video" controls> <source src="stream?video=video.webm" /> </video> </div> </body> </html>
You'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
- GitHub - pseudostreaming
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.