maximum bitrate control

Hello,

we have a vod setup (wowza3 + jwplayer) with some amount of mobile customers. since mobile customers are very sensitive to the data traffic volumes, we would like to introduce an option for them to be able to limit their maximum bitrate. a typical smil file would contain links to 7-8 different files encoded with bitrates up to 4-5 Mbits/s. However typical mobile customer might be fine with 1 Mbit/s bitrate for some type of the content.

One of the ways to solve this problem would be to create different smil-files each containing increased number of links to the vod files. So,

a1.smil will contain link to the 1st lowest bitrate file

a2.smil will contain link to the 1st lowest bitrate file and the second lowest bitrate file

etc

However this approach creates other difficulties with other setup. Due to some technical reasons we cannot use wowza-generated jwplayer rss files, but generate them on our own. Our content is usually having 3-4 different audio-streams, so we generally have b0.rss pointing to the smil file with audioindex=0, b1.rss point to the smil file with audioindex=1 etc

as soon as we combine those 2 approaches we will end up with huge amount of rss and smil files pointing to different bitrates and audioindexes, which is hard to manage in the end.

So, my question is - how is it possible to control maximum bitrate which can be played back with specific url arguments to the vod url? i.e. audioindex argument provides a way to select correct audio stream, is there something like maxbitrate argument which will limit playback to the specified bitrate or is there any other solution which can be used in our setup which will not generate few dozens of smil and rss files?

Hi,

There is no built in method but you could probably do this using a custom module.

One approach would be to create a custom MediaList implementation that would return the smil based on what the player requests.

Please see the following article for information.

How to use Java API calls to resolve SMIL file requests (AMLST)

Roger.

The IMediaListProvider API (amlst method), is not going to work correctly with HTTPOrigin mode turned on. The CDN that is caching is going to cache the smil file too. Clients are expected to stream from the CDN, not Wowza.

Richard

Roman,

I think an initial request from the CDN should run the IMediaListProvider, but no, what you describe, dynamic MediaList per playback session, is not going to work with HTTP Origin mode. Clients are streaming from the CDN cache, not Wowza. Yes, also, using an audioindex and other techniques involving Wowza directly with the playback client will not work.

Richard

Hi,

Wowza is able to automatically generate the JW Player rss file using the information generated by the MediaList API.

You would use the following url format in your player.

http://<wowza-ip>:1935/<app-name>/amlst:<stream-name>/jwplayer.rss

When this is called, it will call the resolveMediaList method and your code would return the required renditions.

There is also a jwplayer.smil url for JW Player 6 and a medialist.smil url that returns a regular smil file.

Roger.

Hi,

Take a look at the following article.

Source code for HTTPProviderMediaList in Wowza Media Server

It is the source code for the MediaList HTTP Provider that I referred to earlier. It shows how to load an existing smil file into a MediaList structure that you can then modify and return the modified media list.

MediaList mediaList = MediaListUtils.parseMediaList(appInstance, streamName, streamExt, null);

This line loads an existing smil file referenced by streamName and streamExt. You could use this method in the AMLST resolveMediaList method to load an existing smil instead of creating a new one from scratch.

The resolveMediaList method gets called for every request so you can customise the response to each player by using query variables.

Roger.

Hi,

The reason you are getting an error is because you are passing a null appInstance to the parseMediaList method.

There are a number of ways you can get the appInstance but in this case, because you are creating a module that is linked to a single appInstance, the easiest is to create a instance level field and reference it with this.appInstance.

In onAppStart, add the following line.

this.appInstance = appInstance;

You will also have to add a field to the class and the ide will most likely prompt you to do so.

You do not need this bit that you copied from the HTTPProvider.

			IHTTPStreamerSession  	HTTPClient = null;
			try { HTTPClient = stream.getHTTPStreamerSession(); } catch (Exception client) {}
			
			IVHost vhost = HTTPClient.getVHost();
			
			String comboAppStr = null;
			String applicationStr = comboAppStr;
	        String appInstanceStr = IApplicationInstance.DEFAULT_APPINSTANCE_NAME;
	        
	        int cindex = applicationStr.indexOf("/");
	        if (cindex > 0)
	        {
	        	appInstanceStr = applicationStr.substring(cindex+1);
	        	applicationStr = applicationStr.substring(0, cindex);
	        }
	        
	        
			IApplication applicationLoc = vhost.getApplication(applicationStr);
			
			IApplicationInstance appInstance = applicationLoc.getAppInstance(appInstanceStr);

The reason the HTTP Provider does it this way is because it does not already have an appInstance reference and also, it is running at a VHost level so can be used on all appInstances.

There currently isn’t any high level documentation that provides component mapping. but the following rules generally apply.

There is one Server.

There can be multiple VHosts.

Each VHost can run multiple Applications.

Each Application can have multiple AppInstances but there generally is just one.

Almost everything else is related to the AppInstance.

Clients are only related to RTMP connections.

HTTP & RTP connections have sessions, not clients.

Streams can have one of Client or httpSession or rtpSession or none of the above if it is an internal stream.

The best place to look is in the Server API documentation at each of the main interfaces and at the example code on the forum for different implementations.

By all means, if you are not sure then please do ask.

Roger.

Hi Roman,

Query params do not work with http origin mode enabled because Wowza creates the playback stream based on the stream name and not the query params. With http origin, the first request for a stream name will create the playback stream and that request’s params will be used. A future request for the same stream name but with different params will not create a new stream and it’s params will be ignored. The only exception is the DVR param used to turn on DVR mode.

There is a workaround however. If you pass a unique stream name into Wowza then that will create a new stream each time. You can then use the StreamNameAlias api to map your unique name to the real stream name.

I would suggest implementing the IMediaStreamNameAliasProvider interface and then remap the names in the resolvePlayAlias method. If you need separate mappings for different player types then use the IMediaStreamNameAliasProvider2 interface which has separate resolvePlayAlias methods for each player type.

This method will be called for the smil file as well as the mp4 files. You should just have to remap the smil file names and pass thru the mp4 file names unchanged.

Roger.

Thank you Roger,

this is what we were looking for.

Just another question - is it possible to customize the code responsible for jwplayer rss generation by means of creating custom module?

Hi,

There is no built in method but you could probably do this using a custom module.

One approach would be to create a custom MediaList implementation that would return the smil based on what the player requests.

Please see the following article for information.

How to use Java API calls to resolve SMIL file requests (AMLST)

Roger.

Roger, one more question here.

Basically to achieve dynamic smil generation for our case, we still need to read reference smil file, get http request params and according to this generate chunklist reply with segment available only for this particular customer.

this article provides good example of handling http request params: Select multiple tracks from a VOD file

however I couldn’t find any example on smil file reading and manipulation. can you help with any example or a link?

Hi,

Take a look at the following article.

Source code for HTTPProviderMediaList in Wowza Media Server

It is the source code for the MediaList HTTP Provider that I referred to earlier. It shows how to load an existing smil file into a MediaList structure that you can then modify and return the modified media list.

MediaList mediaList = MediaListUtils.parseMediaList(appInstance, streamName, streamExt, null);

This line loads an existing smil file referenced by streamName and streamExt. You could use this method in the AMLST resolveMediaList method to load an existing smil instead of creating a new one from scratch.

The resolveMediaList method gets called for every request so you can customise the response to each player by using query variables.

Roger.

Thanks a lot, Roger!

This example answers all my questions.

I tried to incorporate the code from HTTPProviderMediaList to the ResolveMediaList

I unded up with the following package:

package com.wowza.wms.plugin.test.module;
import com.wowza.wms.httpstreamer.model.IHTTPStreamerSession;
import com.wowza.wms.medialist.*;
import com.wowza.wms.module.*;
import com.wowza.wms.stream.*;
import com.wowza.wms.util.MediaListUtils;
import com.wowza.wms.vhost.IVHost;
import com.wowza.wms.application.*;
public class ModuleAMLSTTest extends ModuleBase 
{
	class MyMediaListProvider implements IMediaListProvider
	{
		public MediaList resolveMediaList(IMediaListReader mediaListReader, IMediaStream stream, String streamName)
		{
			/*
			MediaList mediaList = new MediaList();
			
			MediaListSegment segment = new MediaListSegment();
			mediaList.addSegment(segment);
			MediaListRendition rendition1 = new MediaListRendition();
			segment.addRendition(rendition1);
			
			rendition1.setName("mp4:"+streamName+"_400k.mp4");
			rendition1.setBitrateAudio(128000);
			rendition1.setBitrateVideo(400000);
			rendition1.setWidth(320);
			rendition1.setHeight(240);
			rendition1.setAudioCodecId("mp4a.40.2");
			rendition1.setVideoCodecId("avc1.66.12");
					
			MediaListRendition rendition2 = new MediaListRendition();
			segment.addRendition(rendition2);
			
			rendition2.setName("mp4:"+streamName+"_800k.mp4");
			rendition2.setBitrateAudio(128000);
			rendition2.setBitrateVideo(800000);
			rendition2.setWidth(640);
			rendition2.setHeight(480);
			rendition2.setAudioCodecId("mp4a.40.2");
			rendition2.setVideoCodecId("avc1.77.31");
			*/
			//MediaList mediaList = new MediaList();
			IHTTPStreamerSession  	HTTPClient = null;
			try { HTTPClient = stream.getHTTPStreamerSession(); } catch (Exception client) {}
			
			IVHost vhost = HTTPClient.getVHost();
			
			String comboAppStr = null;
			String applicationStr = comboAppStr;
	        String appInstanceStr = IApplicationInstance.DEFAULT_APPINSTANCE_NAME;
	        
	        int cindex = applicationStr.indexOf("/");
	        if (cindex > 0)
	        {
	        	appInstanceStr = applicationStr.substring(cindex+1);
	        	applicationStr = applicationStr.substring(0, cindex);
	        }
	        
	        
			IApplication applicationLoc = vhost.getApplication(applicationStr);
			
			IApplicationInstance appInstance = applicationLoc.getAppInstance(appInstanceStr);
			
			String streamExt = "smil";
			MediaList mediaList = MediaListUtils.parseMediaList(appInstance, streamName, streamExt, null);
			return mediaList;
		}
	}
	
	public void onAppStart(IApplicationInstance appInstance)
	{
		appInstance.setMediaListProvider(new MyMediaListProvider());
	}
	
}

it’s compiled without errors, however trying to get request http://127.0.0.1:1935/vod/amlst:sample/playlist.m3u8

gives ERROR server comment - HTTPStreamerCupertinoIndexPlaylist.indexFile[vod/definst/amlst:sample]: java.lang.NullPointerException

Looks like the problem is related to incorrect mediaList initialization due to incorrect appInstance initialization.

I tried to play around with getAppInstance() also

IClient client;

IApplicationInstance appInstance = client.getAppInstance();

but cannot find correct way of getting client structure.

plz help.

btw,

do you have any kind of simplified workflow of wowza request processing with mapping to the core components - i.e. http request is coming to the system via classA.functionB, afterwards dispatched by classC.functionD based on XX to YY or ZZ etc etc?

API document contains only api calls details but doesn’t provide any ‘big map’ of system workflow.

Thanks for the help, Roger.

here is the resulting module. feel free to publish it in case someone might be interested in the functionality.

/* 
 * (C)opyleft 2013 <roman.hlynovskiy@gmail.com>
 * This module will strip out all chunks having higher bitrate than specified in "maxbitrate" http request based on initial smil file containing all chunks details
 * request example: [url]http://127.0.0.1:1935/vod/amlst:sample/playlist.m3u8?maxbitrate=2864000[/url]
 */
package com.wowza.wms.plugin.test.module;
import java.util.*;
import com.wowza.util.*;
import com.wowza.wms.http.HTTPProviderMediaList;
import com.wowza.wms.httpstreamer.model.IHTTPStreamerSession;
import com.wowza.wms.logging.WMSLoggerFactory;
import com.wowza.wms.medialist.*;
import com.wowza.wms.module.*;
import com.wowza.wms.stream.*;
import com.wowza.wms.util.MediaListUtils;
import com.wowza.wms.application.*;
public class ModuleAMLSTTest extends ModuleBase 
{
	IApplicationInstance appInstance = null;
	public static final String PROPERTY_maxbitrate = "maxbitrate";
	
	class MyMediaListProvider implements IMediaListProvider
	{
		public MediaList resolveMediaList(IMediaListReader mediaListReader, IMediaStream stream, String streamName)
		{			
			
			//WMSLoggerFactory.getLogger(ModuleAMLSTTest.class).warn("ModuleAMLSTTest.resolveMediaList: Loading BFG9K...");
			
			IHTTPStreamerSession  	HTTPClient = null;
			try { HTTPClient = stream.getHTTPStreamerSession(); } catch (Exception client) {}
			int maxBitrate = 0;
			
			
			if ( HTTPClient != null ) {
				String queryStr = HTTPClient.getQueryStr();
				Map<String, String> queryParams = HTTPUtils.splitQueryStr(queryStr);
				
				String indexStr = PROPERTY_maxbitrate;
				
				if (queryParams.containsKey(indexStr)) 
					maxBitrate = Integer.parseInt(queryParams.get(indexStr));
                
			}
			
			String streamExt = "smil";
			//WMSLoggerFactory.getLogger(ModuleAMLSTTest.class).warn("stream name is " + streamName);
			
			MediaList mediaList = MediaListUtils.parseMediaList(appInstance, streamName, streamExt, null);
			if (maxBitrate != 0) {
				//WMSLoggerFactory.getLogger(ModuleAMLSTTest.class).warn("max bitrate is set to: "+ maxBitrate);
				List<MediaListSegment> segments = mediaList.getSegment();
				for (MediaListSegment segment : segments) {
					List<MediaListRendition> renditions = segment.getRenditions();
					
					for (MediaListRendition rendition : renditions) {
						int totalBitrate = rendition.getBitrateTotal();
	
						if (totalBitrate > maxBitrate)
							segment.removeRendition(rendition);
	
					}
				}
				
			}
							
			return mediaList;
		}
	}
	
	public void onAppStart(IApplicationInstance appInstance)
	{
		this.appInstance = appInstance;
		appInstance.setMediaListProvider(new MyMediaListProvider());
	}
	
}

Hello Roger,

it seems there is a strange problem with compatibility of this module with httpOriginMode. As soon as httpOrigin is turned on, it seems that wowza somehow starts caching smil replies which causes incorrect behavior of the module.

here is example:

requesting full list

root@dev:~# wget -qO- “http://127.0.0.1:1935/vod/jr01/video_1328530578/transcoded/amlst:m.smil/playlist.m3u8

#EXTM3U

#EXT-X-VERSION:3

#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=464000,RESOLUTION=432x240

chunklist_b464000.m3u8

#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=314000,RESOLUTION=288x160

chunklist_b314000.m3u8

#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=728000,RESOLUTION=480x270

chunklist_b728000.m3u8

#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=1264000,RESOLUTION=640x360

chunklist_b1264000.m3u8

#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=1864000,RESOLUTION=848x480

chunklist_b1864000.m3u8

requesting limited bitrate list, still whole list is returned

root@dev:~# wget -qO- “http://127.0.0.1:1935/vod/jr01/video_1328530578/transcoded/amlst:m.smil/playlist.m3u8?maxbitrate=728000

#EXTM3U

#EXT-X-VERSION:3

#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=464000,RESOLUTION=432x240

chunklist_b464000.m3u8?maxbitrate=728000

#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=314000,RESOLUTION=288x160

chunklist_b314000.m3u8?maxbitrate=728000

#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=728000,RESOLUTION=480x270

chunklist_b728000.m3u8?maxbitrate=728000

#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=1264000,RESOLUTION=640x360

chunklist_b1264000.m3u8?maxbitrate=728000

#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=1864000,RESOLUTION=848x480

chunklist_b1864000.m3u8?maxbitrate=728000

requesting limited bitrate list again, now it works!

root@dev:~# wget -qO- “http://127.0.0.1:1935/vod/jr01/video_1328530578/transcoded/amlst:m.smil/playlist.m3u8?maxbitrate=728000

#EXTM3U

#EXT-X-VERSION:3

#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=464000,RESOLUTION=432x240

chunklist_b464000.m3u8?maxbitrate=728000

#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=314000,RESOLUTION=288x160

chunklist_b314000.m3u8?maxbitrate=728000

#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=728000,RESOLUTION=480x270

chunklist_b728000.m3u8?maxbitrate=728000

requesting another limited bitrate list again, it works!

root@dev:~# wget -qO- “http://127.0.0.1:1935/vod/jr01/video_1328530578/transcoded/amlst:m.smil/playlist.m3u8?maxbitrate=1264000

#EXTM3U

#EXT-X-VERSION:3

#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=464000,RESOLUTION=432x240

chunklist_b464000.m3u8?maxbitrate=1264000

#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=314000,RESOLUTION=288x160

chunklist_b314000.m3u8?maxbitrate=1264000

#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=728000,RESOLUTION=480x270

chunklist_b728000.m3u8?maxbitrate=1264000

#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=1264000,RESOLUTION=640x360

chunklist_b1264000.m3u8?maxbitrate=1264000

requesting first limited bitrate list again, it still returns result for a previous query

root@dev:~# wget -qO- “http://127.0.0.1:1935/vod/jr01/video_1328530578/transcoded/amlst:m.smil/playlist.m3u8?maxbitrate=728000

#EXTM3U

#EXT-X-VERSION:3

#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=464000,RESOLUTION=432x240

chunklist_b464000.m3u8?maxbitrate=728000

#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=314000,RESOLUTION=288x160

chunklist_b314000.m3u8?maxbitrate=728000

#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=728000,RESOLUTION=480x270

chunklist_b728000.m3u8?maxbitrate=728000

#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=1264000,RESOLUTION=640x360

chunklist_b1264000.m3u8?maxbitrate=728000

also what I see, the requests with incorrect answers are not getting logged at all. also if I wait from 5 to 10 seconds between each request, the answers are correct.

is it normail wowza behavior or a “feature”?

Hello Richard,

it seems I’m trying to achieve something which was never expected to happen - different smil structures with the same request.

while httpOrigin is on, wowza expects upfront CDN to cache smil requests. Our CDN is ok to cache those requests, but they should definitely correct (which is not the case here).

is it possible to introduce a feature in the next minor release similar to cupertinoCacheControlPlaylist max-age property for the smil files or some other workaround which would make wowza understand that a query with params is not the same as the query without params.

p.s. wouldn’t httporigin mode also break ‘audioindex’ support from wms-plugin-collection ? I feel it would be the same issue since wowza will continue to provide the same chunks upon same request even with different params?

Roman

Richard, Roger

thanks for all those clarifications. after all those suggestions we found another way to fix a problem.

we do use fixed names for smil files naming, so this line from the module:

MediaList mediaList = MediaListUtils.parseMediaList(appInstance, streamName, streamExt, null);

changes to:

MediaList mediaList = MediaListUtils.parseMediaList(appInstance, “media”, streamExt, null);

and a request for unique smil is: http://127.0.0.1:1935/vod/amlst:RANDOM_STRING/playlist.m3u8?maxbitrate=2864000

while RANDOM_STRING is really random it works like a charm :slight_smile: