Hi all.
I’m trying to write a plugin that will allow us to switch live streams, initially to insert a looping ‘paused’ stream, but in the limit to allow swapping between other input stream sources. I’ve set things up as per: https://www.wowza.com/docs/how-to-switch-streams-using-stream-class-streams I’ve got one input (RTMP) source and an MP4 as separate playlists which I can swap between. When I deploy this, it works great for RTMP clients, swapping between the sources is quick and seamless.
However, for clients viewing over HLS, the experience isn’t so great. When swapping between playlists, the HLS player (Quicktime or an iPad 3) sometimes freezes for up to 7-8 seconds, and often ‘flickers’ between the two input streams for what appears to be about a segment (10s or so.) I understood there can be problems with transitions if the streams are different dimensions, bitrates, so I’ve tried to ensure they are the same. The MP4 is a result of a webcam recording from Flash into Wowza, as is the live stream. The load in the machine running Wowza is low:
top - 16:34:50 up 1 day, 22:52, 3 users, load average: 0.04, 0.17, 0.14
So I don’t think it’s running out of resource.
Do you have any ideas as to what could be going on?
Thanks,
Henry
FWIW, the code for my plugin is:
public class StreamSwitchWowzaHTTPProvider extends HTTProvider2Base {
@Override
public void onHTTPRequest(IVHost vHost, IHTTPRequest httpRequest, IHTTPResponse httpResponse) {
if (!authenticate(httpRequest)) {
writeError(403, httpResponse, "Cannot authenticate request");
return;
}
try {
String action = readRequiredParameter(httpRequest, "action");
switch (action) {
case "setup":
setupLiveStreams(vHost, httpRequest, httpResponse);
break;
case "switch":
switchLiveStreams(vHost, httpRequest, httpResponse);
break;
case "version":
writeSuccess(200, httpResponse, "StreamSwitchWowzaHTTPProvider v1.00: " + System.currentTimeMillis());
break;
default:
writeError(400, httpResponse, "Action not recognised: " + action);
break;
}
} catch (MissingParameterException mpe) {
writeError(400, httpResponse, "Missing required parameter: " + mpe.getParameterName());
}
}
private void setupLiveStreams(IVHost vHost, IHTTPRequest httpRequest, IHTTPResponse httpResponse) throws MissingParameterException {
String applicationName = readRequiredParameter(httpRequest, "applicationName");
IApplication application = vHost.getApplication(applicationName);
IApplicationInstance applicationInstance = application.getAppInstance("_definst_");
String outputStreamName = readRequiredParameter(httpRequest, "outputStreamName");
String inputStreamName = readRequiredParameter(httpRequest, "inputStreamName");
writeLog("Starting streaming from inputStreamName: " + inputStreamName + " to outputStreamName: " + outputStreamName);
Playlist inputStreamPlaylist = new Playlist(inputStreamName + "_source_playlist");
inputStreamPlaylist.addItem(inputStreamName, -2, -1);
inputStreamPlaylist.setRepeat(true);
applicationInstance.getProperties().setProperty(inputStreamPlaylist.getName(), inputStreamPlaylist);
// FIXME: filename should be included in request.
Playlist pauseStreamPlaylist = new Playlist(inputStreamName + "_paused_playlist");
pauseStreamPlaylist.addItem("mp4:waiting.mp4", 0, -1);
pauseStreamPlaylist.setRepeat(true);
applicationInstance.getProperties().setProperty(pauseStreamPlaylist.getName(), pauseStreamPlaylist);
Stream outputStream = Stream.createInstance(applicationInstance, outputStreamName);
applicationInstance.getProperties().setProperty(outputStreamName, outputStream);
// Start streaming from playlist to stream.
pauseStreamPlaylist.open(outputStream);
writeSuccess(200, httpResponse, "Successfully setup live streams.");
}
private void switchLiveStreams(IVHost vHost, IHTTPRequest httpRequest, IHTTPResponse httpResponse) throws MissingParameterException {
String applicationName = readRequiredParameter(httpRequest, "applicationName");
IApplication application = vHost.getApplication(applicationName);
IApplicationInstance applicationInstance = application.getAppInstance("_definst_");
String outputStreamName = readRequiredParameter(httpRequest, "outputStreamName");
String playlistName = readRequiredParameter(httpRequest, "playlistName");
Stream outputStream = (Stream)applicationInstance.getProperties().getProperty(outputStreamName);
Playlist playlist = (Playlist)applicationInstance.getProperties().getProperty(playlistName);
playlist.open(outputStream);
writeSuccess(200, httpResponse, "Successfully switched live streams.");
}
private boolean authenticate(@SuppressWarnings("unused") IHTTPRequest iHttpRequest) {
// TODO: Implement!
return true;
}
private String readRequiredParameter(IHTTPRequest iHttpRequest, String parameterName) throws MissingParameterException {
String parameterValue = iHttpRequest.getParameter(parameterName);
if (parameterValue == null) {
throw new MissingParameterException("Parameter: " + parameterName + " not specified.", parameterName);
}
return parameterValue;
}
private void writeResponse(int statusCode, IHTTPResponse resp, String response) {
try {
resp.setResponseCode(statusCode);
OutputStream out = resp.getOutputStream();
byte[] outBytes = response.getBytes();
out.write(outBytes);
writeLog("Responded with HTTP " + statusCode + ": " + response);
}
catch (Throwable t) {
resp.setResponseCode(500);
writeLog("Responded with HTTP 500: Unknown error: " + t.getMessage());
}
}
private void writeSuccess(int statusCode, IHTTPResponse resp, String response) {
writeResponse(statusCode, resp, "[OK] " + response);
}
private void writeError(int statusCode, IHTTPResponse resp, String response) {
writeLog("Request to StreamSwitchWowzaHTTPProvider failed: " + response);
writeResponse(statusCode, resp, "[ERROR] " + response);
}
private void writeLog(String message) {
WMSLoggerFactory.getLogger(null).info("[" + this.getClass().getName() + "] " + message);
}
private static class MissingParameterException extends Exception {
private String parameterName;
MissingParameterException(String message, String parameterName) {
super(message);
this.parameterName = parameterName;
}
String getParameterName() {
return parameterName;
}
}
}