Skip to content

Commit

Permalink
Close videojsGH-605: added RTMP support. fixes videojs#559.
Browse files Browse the repository at this point in the history
  • Loading branch information
iamjem authored and heff committed Aug 23, 2013
1 parent 5a6fa37 commit 7ab3d19
Show file tree
Hide file tree
Showing 8 changed files with 176 additions and 9 deletions.
18 changes: 18 additions & 0 deletions docs/tech.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,24 @@ When adding additional Tech to a video player, make sure to add the supported te
techOrder: ["html5", "flash", "other supported tech"]
});

Flash Technology
==================
The Flash playback tech is a part of the default `techOrder`. You may notice undesirable playback behavior in browsers that are subject to using this playback tech, in particular when scrubbing and seeking within a video. This behavior is a result of Flash's progressive video playback.

Enabling Streaming Playback
--------------------------------
In order to force the Flash tech to choose streaming playback, you need to provide a valid streaming source **before other valid Flash video sources**. This is necessary because of the source selection algorithm, where playback tech chooses the first possible source object with a valid type. Valid streaming `type` values include `rtmp/mp4` and `rtmp/flv`. The streaming `src` value requires valid connection and stream strings, separated by an `&`. An example of supplying a streaming source through your HTML markup might look like:

<source src="rtmp://your.streaming.provider.net/cfx/st/&mp4:path/to/video.mp4" type="rtmp/mp4">
<source src="http://your.static.provider.net/path/to/video.mp4" type="video/mp4">
<source src="http://your.static.provider.net/path/to/video.webm" type="video/webm">

You may optionally use the last `/` as the separator between connection and stream strings, for example:

<source src="rtmp://your.streaming.provider.net/cfx/st/mp4:video.mp4" type="rtmp/mp4">

All four RTMP protocols are valid in the `src` (RTMP, RTMPT, RTMPE, and RTMPS).

Youtube Technology
==================
To add a youtube source to your video tag, use the following source:
Expand Down
20 changes: 19 additions & 1 deletion src/js/control-bar/progress-control.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,25 @@ vjs.SeekBar.prototype.updateARIAAttributes = function(){
};

vjs.SeekBar.prototype.getPercent = function(){
return this.player_.currentTime() / this.player_.duration();
var currentTime;
// Flash RTMP provider will not report the correct time
// immediately after a seek. This isn't noticeable if you're
// seeking while the video is playing, but it is if you seek
// while the video is paused.
if (this.player_.techName === 'Flash' && this.player_.seeking()) {
var cache = this.player_.getCache();
if (cache.lastSetCurrentTime) {
currentTime = cache.lastSetCurrentTime;
}
else {
currentTime = this.player_.currentTime();
}
}
else {
currentTime = this.player_.currentTime();
}

return currentTime / this.player_.duration();
};

vjs.SeekBar.prototype.onMouseDown = function(event){
Expand Down
94 changes: 87 additions & 7 deletions src/js/media/flash.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,14 @@ vjs.Flash = vjs.MediaTechController.extend({

// If source was supplied pass as a flash var.
if (source) {
flashVars['src'] = encodeURIComponent(vjs.getAbsoluteURL(source.src));
if (source.type && vjs.Flash.isStreamingType(source.type)) {
var parts = vjs.Flash.streamToParts(source.src);
flashVars['rtmpConnection'] = encodeURIComponent(parts.connection);
flashVars['rtmpStream'] = encodeURIComponent(parts.stream);
}
else {
flashVars['src'] = encodeURIComponent(vjs.getAbsoluteURL(source.src));
}
}

// Add placeholder to player div
Expand Down Expand Up @@ -224,10 +231,16 @@ vjs.Flash.prototype.pause = function(){
};

vjs.Flash.prototype.src = function(src){
// Make sure source URL is abosolute.
src = vjs.getAbsoluteURL(src);

this.el_.vjs_src(src);
if (vjs.Flash.isStreamingSrc(src)) {
src = vjs.Flash.streamToParts(src);
this.setRtmpConnection(src.connection);
this.setRtmpStream(src.stream);
}
else {
// Make sure source URL is abosolute.
src = vjs.getAbsoluteURL(src);
this.el_.vjs_src(src);
}

// Currently the SWF doesn't autoplay if you load a source later.
// e.g. Load player w/ no source, wait 2s, set src.
Expand All @@ -237,6 +250,20 @@ vjs.Flash.prototype.src = function(src){
}
};

vjs.Flash.prototype.currentSrc = function(){
var src = this.el_.vjs_getProperty('currentSrc');
// no src, check and see if RTMP
if (src == null) {
var connection = this.rtmpConnection(),
stream = this.rtmpStream();

if (connection && stream) {
src = vjs.Flash.streamFromParts(connection, stream);
}
}
return src;
};

vjs.Flash.prototype.load = function(){
this.el_.vjs_load();
};
Expand All @@ -260,7 +287,7 @@ vjs.Flash.prototype.enterFullScreen = function(){

// Create setters and getters for attributes
var api = vjs.Flash.prototype,
readWrite = 'preload,currentTime,defaultPlaybackRate,playbackRate,autoplay,loop,mediaGroup,controller,controls,volume,muted,defaultMuted'.split(','),
readWrite = 'rtmpConnection,rtmpStream,preload,currentTime,defaultPlaybackRate,playbackRate,autoplay,loop,mediaGroup,controller,controls,volume,muted,defaultMuted'.split(','),
readOnly = 'error,currentSrc,networkState,readyState,seeking,initialTime,duration,startOffsetTime,paused,played,seekable,ended,videoTracks,audioTracks,videoWidth,videoHeight,textTracks'.split(',');
// Overridden: buffered

Expand Down Expand Up @@ -301,7 +328,7 @@ vjs.Flash.isSupported = function(){
};

vjs.Flash.canPlaySource = function(srcObj){
if (srcObj.type in vjs.Flash.formats) { return 'maybe'; }
if (srcObj.type in vjs.Flash.formats || srcObj.type in vjs.Flash.streamingFormats) { return 'maybe'; }
};

vjs.Flash.formats = {
Expand All @@ -311,6 +338,11 @@ vjs.Flash.formats = {
'video/m4v': 'MP4'
};

vjs.Flash.streamingFormats = {
'rtmp/mp4': 'MP4',
'rtmp/flv': 'FLV'
};

vjs.Flash['onReady'] = function(currSwf){
var el = vjs.el(currSwf);

Expand Down Expand Up @@ -447,3 +479,51 @@ vjs.Flash.getEmbedCode = function(swf, flashVars, params, attributes){

return objTag + attrsString + '>' + paramsString + '</object>';
};

vjs.Flash.streamFromParts = function(connection, stream) {
return connection + '&' + stream;
};

vjs.Flash.streamToParts = function(src) {
var parts = {
connection: '',
stream: ''
};

if (! src) {
return parts;
}

// Look for the normal URL separator we expect, '&'.
// If found, we split the URL into two pieces around the
// first '&'.
var connEnd = src.indexOf('&');
var streamBegin;
if (connEnd !== -1) {
streamBegin = connEnd + 1;
}
else {
// If there's not a '&', we use the last '/' as the delimiter.
connEnd = streamBegin = src.lastIndexOf('/') + 1;
if (connEnd === 0) {
// really, there's not a '/'?
connEnd = streamBegin = src.length;
}
}
parts.connection = src.substring(0, connEnd);
parts.stream = src.substring(streamBegin, src.length);

return parts;
};

vjs.Flash.isStreamingType = function(srcType) {
return srcType in vjs.Flash.streamingFormats;
};

// RTMP has four variations, any string starting
// with one of these protocols should be valid
vjs.Flash.RTMP_RE = /^rtmp[set]?:\/\//i;

vjs.Flash.isStreamingSrc = function(src) {
return vjs.Flash.RTMP_RE.test(src);
};
1 change: 1 addition & 0 deletions src/js/player.js
Original file line number Diff line number Diff line change
Expand Up @@ -915,6 +915,7 @@ vjs.Player.prototype.usingNativeControls = function(bool){

vjs.Player.prototype.error = function(){ return this.techGet('error'); };
vjs.Player.prototype.ended = function(){ return this.techGet('ended'); };
vjs.Player.prototype.seeking = function(){ return this.techGet('seeking'); };

// When the player is first initialized, trigger activity so components
// like the control bar show themselves if needed
Expand Down
Binary file modified src/swf/video-js.swf
Binary file not shown.
3 changes: 2 additions & 1 deletion test/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@
'test/unit/core.js',
'test/unit/media.html5.js',
'test/unit/controls.js',
'test/unit/plugins.js'
'test/unit/plugins.js',
'test/unit/flash.js'
];

var projectRoot = '../';
Expand Down
48 changes: 48 additions & 0 deletions test/unit/flash.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
module('Flash');

var streamToPartsAndBack = function(url) {
var parts = vjs.Flash.streamToParts(url);
return vjs.Flash.streamFromParts(parts.connection, parts.stream);
};

test('test using both streamToParts and streamFromParts', function() {
ok('rtmp://myurl.com/&isthis' === streamToPartsAndBack('rtmp://myurl.com/isthis'));
ok('rtmp://myurl.com/&isthis' === streamToPartsAndBack('rtmp://myurl.com/&isthis'));
ok('rtmp://myurl.com/isthis/&andthis' === streamToPartsAndBack('rtmp://myurl.com/isthis/andthis'));
});

test('test streamToParts', function() {
var parts = vjs.Flash.streamToParts('http://myurl.com/streaming&/is/fun');
ok(parts.connection === 'http://myurl.com/streaming');
ok(parts.stream === '/is/fun');

parts = vjs.Flash.streamToParts('http://myurl.com/&streaming&/is/fun');
ok(parts.connection === 'http://myurl.com/');
ok(parts.stream === 'streaming&/is/fun');

parts = vjs.Flash.streamToParts('http://myurl.com/streaming/is/fun');
ok(parts.connection === 'http://myurl.com/streaming/is/');
ok(parts.stream === 'fun');

parts = vjs.Flash.streamToParts('whatisgoingonhere');
ok(parts.connection === 'whatisgoingonhere');
ok(parts.stream === '');

parts = vjs.Flash.streamToParts();
ok(parts.connection === '');
ok(parts.stream === '');
});

test('test isStreamingSrc', function() {
var isStreamingSrc = vjs.Flash.isStreamingSrc;
ok(isStreamingSrc('rtmp://streaming.is/fun'));
ok(isStreamingSrc('rtmps://streaming.is/fun'));
ok(isStreamingSrc('rtmpe://streaming.is/fun'));
ok(isStreamingSrc('rtmpt://streaming.is/fun'));
// test invalid protocols
ok(!isStreamingSrc('rtmp:streaming.is/fun'));
ok(!isStreamingSrc('rtmpz://streaming.is/fun'));
ok(!isStreamingSrc('http://streaming.is/fun'));
ok(!isStreamingSrc('https://streaming.is/fun'));
ok(!isStreamingSrc('file://streaming.is/fun'));
});
1 change: 1 addition & 0 deletions test/unit/mediafaker.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ vjs.MediaFaker.prototype.createEl = function(){
};

vjs.MediaFaker.prototype.currentTime = function(){ return 0; };
vjs.MediaFaker.prototype.seeking = function(){ return false; };
vjs.MediaFaker.prototype.volume = function(){ return 0; };
vjs.MediaFaker.prototype.muted = function(){ return false; };
vjs.MediaFaker.prototype.pause = function(){ return false; };
Expand Down

0 comments on commit 7ab3d19

Please sign in to comment.