How to proxy a HTTP video stream to any amount of clients through a Spring Webserver
Solution 1
I am not sure what kind of source you are using for generating your video stream (live camera or a video file or youtube video or ..)
You can probably use StreamingResponseBody (requires Spring 4.2+). Refer following links
http://www.logicbig.com/tutorials/spring-framework/spring-web-mvc/streaming-response-body/
http://shazsterblog.blogspot.in/2016/02/asynchronous-streaming-request.html
Try this -
@GetMapping("/stream1")
@ResponseBody
public StreamingResponseBody getVidoeStream1(@RequestParam String any) throws IOException {
/* do security check before connecting to stream hosting server */
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<Resource> responseEntity = restTemplate.exchange( "http://localhost:8080/stream", HttpMethod.GET, null, Resource.class );
InputStream st = responseEntity.getBody().getInputStream();
return (os) -> {
readAndWrite(st, os);
};
}
private void readAndWrite(final InputStream is, OutputStream os)
throws IOException {
byte[] data = new byte[2048];
int read = 0;
while ((read = is.read(data)) > 0) {
os.write(data, 0, read);
}
os.flush();
}
It should work. You can write your own implementation of readAndWrite() depending on your requirements.
So, your spring proxy controller could be something like this...
@Controller
public class HttpStreamProxyController {
@RequestMapping("/spring")
@ResponseBody
public StreamingResponseBody getSecuredHttpStream() {
if (clientIsSecured) {
//... Security information
RestTemplate restTemplate = new RestTemplate();
// get video stream by connecting to stream hosting server like this
ResponseEntity<Resource> responseEntity = restTemplate.exchange( "https://ur-to-stream", HttpMethod.GET, null, Resource.class );
InputStream st = responseEntity.getBody().getInputStream();
// Or if there is any other preferred way of getting the video stream use that. The idea is to get the video input stream
// now return a StreamingResponseBody object created by following lambda
return (os) -> {
readAndWrite(st, os);
};
} else {
return null;
}
}
}
The StreamingResponseBody returned by your rest endpoint will work fine with HTML5, which could be something like ..
<video width="320" height="240" controls>
<source src="/spring" type="video/mp4">
Your browser does not support the video tag
</video>
Solution 2
I struggled with this same problem for days, attempting to migrate my node app to Spring/React. I'm using a raspberry pi running Motion, which acts as a remote stream server that I was previously able to use the node module mjpeg-proxy
to easily proxy the way OP wishes. This thread was the most useful set of examples in my search to do this in Java, however none of the examples quite worked for me. I'm hoping my controller class and component will help others attempting the same.
Please take note of response.setContentType("multipart/x-mixed-replace; boundary=BoundaryString");
as the boundary=BoundaryString
portion was the last critical piece. I opened the chrome debugger while connecting directly to the stream and this was in the response headers -- when I copied it, suddenly everything worked!
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import javax.servlet.http.HttpServletResponse;
import java.net.URI;
@RestController
@RequestMapping(value = "/video")
public class VideoController {
Logger log = LoggerFactory.getLogger(VideoController.class);
@RequestMapping("/oculus")
public void oculus(HttpServletResponse response) {
log.info("Calling /video/oculus...");
RestTemplate restTemplate = new RestTemplate();
restTemplate.execute(
URI.create("http://oculus:8081"),
HttpMethod.GET,
(ClientHttpRequest request) -> {},
responseExtractor -> {
response.setContentType("multipart/x-mixed-replace; boundary=BoundaryString");
IOUtils.copy(responseExtractor.getBody(), response.getOutputStream());
return null;
}
);
}
@RequestMapping("/door")
public void door(HttpServletResponse response) {
log.info("Calling /video/door...");
RestTemplate restTemplate = new RestTemplate();
restTemplate.execute(
URI.create("http://vox:9002"),
HttpMethod.GET,
clientHttpRequest -> {
clientHttpRequest.getHeaders().add(HttpHeaders.AUTHORIZATION, "Basic blahblahBase64encodedUserAndPass=");
},
responseExtractor -> {
response.setContentType("multipart/x-mixed-replace; boundary=BoundaryString");
IOUtils.copy(responseExtractor.getBody(), response.getOutputStream());
return null;
}
);
}
}
I have included examples for using Motion's basic auth, as well as an unprotected stream.
Both streams are then available on my app using the following bit of react js
class Video extends React.Component{
render() {
return (
<div className="Content">
<p>
<img className="stream" src="/video/oculus"/>
</p>
<p>
<img className="stream" src="/video/door"/>
</p>
</div>
);
}
}
export default Video;```
Solution 3
A good way to do video streaming in a Spring Boot application (I used a different source, however — a USB camera):
@GetMapping(value = "/stream")
public ResponseEntity<StreamingResponseBody> stream() {
StreamingResponseBody stream = outputStream -> {
while (streaming.get()) {
final var raw = streamImage.get();
final var jpeg = convert(byte2Buffered(raw.getImageData(), raw.getImageWidth(), raw.getImageHeight()));
outputStream.write(jpeg);
}
outputStream.flush();
};
final var headers = new HttpHeaders();
headers.add("Access-Control-Allow-Origin", "*");
headers.add("Cache-Control", "no-cache, no-store, must-revalidate");
headers.add("Content-Type", "multipart/x-mixed-replace;boundary=frame");
headers.add("Expires", "0");
headers.add("Pragma", "no-cache");
headers.add("Max-Age", "0");
return ResponseEntity.ok()
.headers(headers)
.body(stream);
}
This way you'll get a stream on URL endpoint, which you can embed into a web page using img
HTML tag.
LeoColman
I'm a brazilian developer and student. Fanatic for the Kotlin language and the Kotest framework.
Updated on July 25, 2022Comments
-
LeoColman over 1 year
Provided that I have a video HTTP stream broadcasted on a server that is on the same network as my Spring Webserver is, for instance in some url such as:
How can I proxy this video stream to any amount of clients, using Spring? The following example demonstrates the wanted flow:
- Spring webserver can be found at http://localhost:9091/spring
- A client wants to access a video stream, so he connects his video-stream-player to http://localhost:9091/spring (the spring webserver)
- The Spring WebServer should redirect the stream found on http://localhost:9090/httpstream to the client, with the latter never knowing that he accessed the httpstream host. The connection is made by Spring, not by the client
This is needed because the HTTPStream is an unsecured and not authenticated host, and I wanted to wrap it around a
Spring Webserver
, so that I could use some form of Security, such as aBasic Auth
.
I tried requesting some form of mapping, but I couldn't find what kind of object to return, nor how to make the connection, but the expected behaviour should be something like this:
@Controller public class HttpStreamProxyController { @RequestMapping("/spring") { public /*Stream Object?*/ getSecuredHttpStream() { if (clientIsSecured) { //... Security information return //What should be returned? } } }
-
LeoColman over 6 yearsThis is likely correct, @vsoni, I'll give it a test and accept this as the answer.
-
LeoColman over 6 yearswhen trying restTemplate.exchange(..) the request hangs, and never go to the next line. Any ideas how to solve it?
-
vsoni over 6 yearsAre you getting any exception on the server? ... OR are you able to get data stream when you access your url from browser directly? I mean the url that you are trying to access thru restTemplate.exchange
-
LeoColman over 6 yearsNo errors, and the request never complete. after a while I get a 503 error on the source (accessing via VLC). I solved it using RestTemplate
execute
method, but am still studying the issue -
Zhandos over 3 yearsCould you provide more details of your solution. It's not clear origin of streaming.get() and streamImage.get(). I'm trying to achieve your approach, but struggling with returning of sequence of jpeg images from camera as live stream .
-
Poliakoff over 3 years@Zhandos
streaming.get()
is a variable ofAtomicBoolean
type which indicates whether there is data available in the stream.streamImage.get();
is a variable of classAtomicReference<V> implements java.io.Serializable
which points to the array of the images. This class with the array of images is provided by the SDK which comes with the device. I suggest checking whether your device can provide something similar or not. -
Zhandos over 3 yearsThank you for your answer, it helped me to achieve my goal.