Some more on Fiddler...which I find very useful and here’s why…
[Note 1: the following information is from my own investigation and may not be entirely correct but works in part for me – take from it what you need]
[Note 2: The example from TVNZ is geo-locked for NZ so try with a site from your region]
[Note 3: Before doing any of the following steps Start Fiddler]
1. Take a content delivery network (CDN) such as Brightcove that is used by an on-demand provider such as http://www.tvnz.co.nz/ondemand to host an rtmpe stream.
Via Google Chrome we can browse to a show which resolves to the friendly URL e.g. http://tvnz.co.nz/the-carrie-diaries/s1-ep11-video-5389240
2. Inspection of the HTTP Response to the page request above in Fiddler shows the following flash player object
http://images.tvnz.co.nz/brightcove/message/message.xml" name="localizedErrorXML"/>
http://tvnz.co.nz/the-carrie-diaries/ s1-ep11-video-5389240" name="linkBaseURL"/>
The key information here is:
swfURL = http://admin.brightcove.com/viewer/us20130328.1037/BrightcoveBootloader.swf
PlayerID = playerID=1029272630001
VideoPlayer = ref: 5389240
3. Immediately following this we see the outbound AMF request (which we need to mirror in our own code):
http://c.brightcove.com/services/messagebroker/amf?playerId=1029272630001
And this request is very important to dissect so that we can extract the rtmpe information.
From Fiddler we can see the browser request content (Fiddler: RAW http request view) which contains the BrightCove class references requiring instantiation in our own code:
- com.brightcove.experience.ViewerExperience
- com.brightcove.experience.ContentOverride
Our URL Service needs to extract the rtmpe FLVFullLengthURL which will look something like this:
Video_URL: [u'rtmpe://cp133145.edgefcs.net/ondemand/', u'mp4:videos/963482467001/963482467001_2250946345001_5380974.mp4']
To do this we need to look closer at the http request http://c.brightcove.com/services/messagebroker/amf?playerId=1029272630001
Fiddler Hex http request view shows us the class properties above and most importantly the value population for each property. In our URL service code we can map this as follows:
class ContentOverride(object):
def __init__ (self, videoPlayer=None):
self.contentType = int(0)
self.contentIds = None
self.target = None
self.contentId = None
self.featuredRefId = None
self.contentRefIds = None
self.featuredId = float('nan')
self.contentRefId = videoPlayer ## ‘5389240’’
class ViewerExperienceRequest(object):
def __init__ (self, url=None, playerID=None, playerKey=None, video_obj=None):
self.experienceId = int(playerID) ## 1029272630001
self.playerKey = ''
self.contentOverrides = []
self.contentOverrides.append(video_obj) ## the ContentOverride object
self.TTLToken = ''
self.URL = url ## http://tvnz.co.nz/the-carrie-diaries/s1-ep11-video-5389240
self.deliveryType = ''
4. Construct the URL Service code to extract the rtmpe including the above class definitions
#########################################################################################
def DoAmfRequest(url):
Log('Get Data From:::: '+url)
data = HTML.ElementFromURL(url).xpath('//object[@class="BrightcoveExperience "]')
if len(data) == 0:
Log('No data')
return None
else:
data = data[0]
playerID = data.xpath('./param[@name="playerID"]')[0].get('value')
Log('PlayerID: '+playerID)
videoPlayer = data.xpath('./param[@name="@videoPlayer"]')[0].get('value').strip('ref:')
result = AmfRequest(url=url, playerID=playerID, playerKey=playerID, videoPlayer=videoPlayer)
return result
#########################################################################################
def AmfRequest(url=None, playerID=None, playerKey=None, videoPlayer=None):
endpoint = AMF_URL ## http://c.brightcove.com/services/messagebroker/amf
if playerKey:
endpoint += '?playerId=%s' % playerKey
client = AMF.RemotingService(url=endpoint, user_agent='', amf_version=3)
service = client.getService('com.brightcove.experience.ExperienceRuntimeFacade')
AMF.RegisterClass(ContentOverride, 'com.brightcove.experience.ContentOverride')
AMF.RegisterClass(ViewerExperienceRequest, 'com.brightcove.experience.ViewerExperienceRequest')
video_obj = ContentOverride(videoPlayer)
experience = ViewerExperienceRequest(url, playerID, playerKey, video_obj)
try:
result = service.getDataForExperience('', experience)
Log(result.items())
return result['programmedContent']['videoPlayer']['mediaDTO']
except:
Log('Exception in AMF Request')
raise Ex.MediaGeoblocked
The request to the service returns the full metadata associated with the media in the result var
result = service.getDataForExperience('', experience)
From the result we can now grab the FLVFullLengthURL along with a number of other useful properties:
[u'rtmpe://cp133145.edgefcs.net/ondemand/', u'mp4:videos/963482467001/963482467001_2250946345001_5380974.mp4']
Now using the downloadable app rtmpDump.exe (http://rtmpdump.mplayerhq.hu/) we can confirm the stream by downloading the file and saving out as a flash player video (flv) which can be played using a video player such as VLC (http://www.videolan.org/vlc/download-windows.html)
Rtmpdump.exe -r "rtmpe://cp133145.edgefcs.net/ondemand/" -a "ondemand" -y "mp4:videos/963482467001/963482467001_2250946345001_5380974.mp4" -o myVideo.flv
5. The final stage is to have Plex play the stream
This is actually the point in my current project which is not working yet but the theory is that the rtmpe is passed back in the callback from your plex playVideo method via RTMPVideoURL
IndirectResponse(VideoClipObject, key=RTMPVideoURL(url=player, clip=playpath, app='ondemand', swf_url=SWF_URL, args=[False, playpath]))
Enabling rtmp in the info.plist and serviceinfo.plist is essential with the following addition to both files:
PlexFrameworkFlags
UseRealRTMP
6. Monitoring the logs
For the URL service I watch the log file:
%LocalAppData%\Plex Media Server\Logs\PMS Plugin Logs\ com.plexapp.system.log
2013-04-02 21:32:58,650 (1b60) : DEBUG (context:198) - Checking for Real RTMP support... Enabled:True Platform:Chrome Product:Web Client Client:0.9.9.13 Server:0.9.7.19-3baac57
2013-04-02 21:32:58,651 (1b60) : DEBUG (objectkit:754) - Using Real RTMP
2013-04-02 21:32:58,654 (1b60) : DEBUG (runtime:106) - Sending packed state data (99 bytes)
2013-04-02 21:32:58,654 (1b60) : DEBUG (runtime:896) - Response: [200] MediaContainer, 1270 bytes
… and this is where it all stops for me… the video doesn't play from the browser or the windows client for some reason :(
Ok...fixed...playing in clients now...I missed specifying the rtmp protocol
def MediaObjectsForURL(url):
return [
MediaObject(
video_codec = VideoCodec.H264,
audio_codec = AudioCodec.AAC,
container = Container.MP4,
protocol='rtmp',
parts = [PartObject(key=Callback(PlayVideo, url = url))],
audio_channels = 2,
optimized_for_streaming = True
)
]