Browse Source

feat: setup EME key systems for HLS as well as DASH (#657)

Port of #629 against master.
pull/743/head
Brandon Casey 6 years ago
committed by GitHub
parent
commit
af4da1aca7
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 22
      src/util/codecs.js
  2. 33
      src/videojs-http-streaming.js
  3. 6
      test/manifests/demuxed-two.m3u8
  4. 186
      test/videojs-http-streaming.test.js

22
src/util/codecs.js

@ -99,3 +99,25 @@ export const codecsForPlaylist = function(master, media) {
export const isLikelyFmp4Data = (bytes) => {
return findBox(bytes, ['moof']).length > 0;
};
/*
* Check if a codec string refers to an audio codec.
*
* @param {String} codec codec string to check
* @return {Boolean} if this is an audio codec
* @private
*/
export const isAudioCodec = function(codec) {
return (/mp4a\.\d+.\d+/i).test(codec);
};
/**
* Check if a codec string refers to a video codec.
*
* @param {string} codec codec string to check
* @return {boolean} if this is a video codec
* @private
*/
export const isVideoCodec = function(codec) {
return (/avc1\.[\da-f]+/i).test(codec);
};

33
src/videojs-http-streaming.js

@ -36,6 +36,7 @@ import {version as m3u8Version} from 'm3u8-parser/package.json';
import {version as aesVersion} from 'aes-decrypter/package.json';
// import needed to register middleware
import './middleware-set-current-time';
import {isAudioCodec, isVideoCodec} from './util/codecs';
const Hls = {
PlaylistLoader,
@ -134,14 +135,30 @@ const emeKeySystems = (keySystemOptions, videoPlaylist, audioPlaylist) => {
return keySystemOptions;
}
const codecs = {
video: videoPlaylist && videoPlaylist.attributes && videoPlaylist.attributes.CODECS,
audio: audioPlaylist && audioPlaylist.attributes && audioPlaylist.attributes.CODECS
};
if (!codecs.audio && codecs.video.split(',').length > 1) {
codecs.video.split(',').forEach(function(codec) {
codec = codec.trim();
if (isAudioCodec(codec)) {
codecs.audio = codec;
} else if (isVideoCodec(codec)) {
codecs.video = codec;
}
});
}
const videoContentType = codecs.video ? `video/mp4;codecs="${codecs.video}"` : null;
const audioContentType = codecs.audio ? `audio/mp4;codecs="${codecs.audio}"` : null;
// upsert the content types based on the selected playlist
const keySystemContentTypes = {};
for (const keySystem in keySystemOptions) {
keySystemContentTypes[keySystem] = {
audioContentType: `audio/mp4; codecs="${audioPlaylist.attributes.CODECS}"`,
videoContentType: `video/mp4; codecs="${videoPlaylist.attributes.CODECS}"`
};
keySystemContentTypes[keySystem] = {audioContentType, videoContentType};
if (videoPlaylist.contentProtection &&
videoPlaylist.contentProtection[keySystem] &&
@ -161,16 +178,14 @@ const emeKeySystems = (keySystemOptions, videoPlaylist, audioPlaylist) => {
};
const setupEmeOptions = (hlsHandler) => {
if (hlsHandler.options_.sourceType !== 'dash') {
return;
}
const player = videojs.players[hlsHandler.tech_.options_.playerId];
const player = hlsHandler.player_;
if (player.eme) {
const audioPlaylistLoader = hlsHandler.masterPlaylistController_.mediaTypes_.AUDIO.activePlaylistLoader;
const sourceOptions = emeKeySystems(
hlsHandler.source_.keySystems,
hlsHandler.playlists.media(),
hlsHandler.masterPlaylistController_.mediaTypes_.AUDIO.activePlaylistLoader.media()
audioPlaylistLoader && audioPlaylistLoader.media()
);
if (sourceOptions) {

6
test/manifests/demuxed-two.m3u8

@ -0,0 +1,6 @@
#EXTM3U
#EXT-X-VERSION:4
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio",NAME="en",DEFAULT=YES,AUTOSELECT=YES,LANGUAGE="en",URI="media.m3u8"
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=564300,CODECS="mp4a.40.2,avc1.420015",AUDIO="audio"
media1.m3u8

186
test/videojs-http-streaming.test.js

@ -3739,7 +3739,7 @@ QUnit.test('populates quality levels list when available', function(assert) {
);
});
QUnit.test('configures eme if present on selectedinitialmedia', function(assert) {
QUnit.test('configures eme for DASH if present on selectedinitialmedia', function(assert) {
this.player.eme = {
options: {
previousSetting: 1
@ -3758,23 +3758,18 @@ QUnit.test('configures eme if present on selectedinitialmedia', function(assert)
this.clock.tick(1);
this.player.tech_.hls.playlists = {
media: () => {
return {
media: () => ({
attributes: {
CODECS: 'video-codec'
CODECS: 'avc1.420015'
},
contentProtection: {
keySystem1: {
pssh: 'test'
}
}
})
};
},
// mocked for renditions mixin
master: {
playlists: []
}
};
this.player.tech_.hls.masterPlaylistController_.mediaTypes_ = {
SUBTITLES: {},
AUDIO: {
@ -3782,13 +3777,14 @@ QUnit.test('configures eme if present on selectedinitialmedia', function(assert)
media: () => {
return {
attributes: {
CODECS: 'audio-codec'
CODECS: 'mp4a.40.2c'
}
};
}
}
}
};
this.player.tech_.hls.masterPlaylistController_.trigger('selectedinitialmedia');
assert.deepEqual(this.player.eme.options, {
@ -3801,15 +3797,66 @@ QUnit.test('configures eme if present on selectedinitialmedia', function(assert)
keySystems: {
keySystem1: {
url: 'url1',
audioContentType: 'audio/mp4; codecs="audio-codec"',
videoContentType: 'video/mp4; codecs="video-codec"',
audioContentType: 'audio/mp4;codecs="mp4a.40.2c"',
videoContentType: 'video/mp4;codecs="avc1.420015"',
pssh: 'test'
}
}
}, 'set source eme options');
});
QUnit.test('configures eme for HLS if present on selectedinitialmedia', function(assert) {
this.player.eme = {
options: {
previousSetting: 1
}
};
this.player.src({
src: 'manifest/master.m3u8',
type: 'application/x-mpegURL',
keySystems: {
keySystem1: {
url: 'url1'
}
}
});
this.clock.tick(1);
this.player.tech_.hls.playlists = {
media: () => ({
attributes: {
CODECS: 'avc1.420015, mp4a.40.2c'
},
contentProtection: {
keySystem1: {
pssh: 'test'
}
}
})
};
this.player.tech_.hls.masterPlaylistController_.trigger('selectedinitialmedia');
assert.deepEqual(this.player.eme.options, {
previousSetting: 1
}, 'did not modify plugin options');
assert.deepEqual(this.player.currentSource(), {
src: 'manifest/master.m3u8',
type: 'application/x-mpegURL',
keySystems: {
keySystem1: {
url: 'url1',
audioContentType: 'audio/mp4;codecs="mp4a.40.2c"',
videoContentType: 'video/mp4;codecs="avc1.420015"',
pssh: 'test'
}
}
}, 'set source eme options');
});
QUnit.test('integration: configures eme if present on selectedinitialmedia', function(assert) {
QUnit.test('integration: configures eme for DASH if present on selectedinitialmedia', function(assert) {
assert.timeout(3000);
const done = assert.async();
@ -3854,6 +3901,59 @@ QUnit.test('integration: configures eme if present on selectedinitialmedia', fun
this.clock.tick(1);
});
QUnit.test('integration: configures eme for HLS if present on selectedinitialmedia', function(assert) {
assert.timeout(3000);
const done = assert.async();
this.player.eme = {
options: {
previousSetting: 1
}
};
this.player.src({
src: 'demuxed-two.m3u8',
type: 'application/x-mpegURL',
keySystems: {
keySystem1: {
url: 'url1'
}
}
});
this.clock.tick(1);
this.player.tech_.hls.masterPlaylistController_.on('selectedinitialmedia', () => {
assert.deepEqual(this.player.eme.options, {
previousSetting: 1
}, 'did not modify plugin options');
assert.deepEqual(this.player.currentSource(), {
src: 'demuxed-two.m3u8',
type: 'application/x-mpegURL',
keySystems: {
keySystem1: {
url: 'url1',
audioContentType: 'audio/mp4;codecs="mp4a.40.2"',
videoContentType: 'video/mp4;codecs="avc1.420015"'
}
}
}, 'set source eme options');
done();
});
// master manifest
this.standardXHRResponse(this.requests.shift());
// video manifest
this.standardXHRResponse(this.requests.shift());
// audio manifest
this.standardXHRResponse(this.requests.shift());
// this allows the audio playlist loader to load
this.clock.tick(1);
});
QUnit.test(
'does not set source keySystems if keySystems not provided by source',
function(assert) {
@ -4409,7 +4509,8 @@ QUnit.module('HLS Integration', {
this.env = useFakeEnvironment(assert);
this.requests = this.env.requests;
this.mse = useFakeMediaSource();
this.tech = new (videojs.getTech('Html5'))({});
this.player = createPlayer();
this.tech = this.player.tech_;
this.clock = this.env.clock;
this.standardXHRResponse = (request, data) => {
@ -4427,6 +4528,7 @@ QUnit.module('HLS Integration', {
this.env.restore();
this.mse.restore();
window.localStorage.clear();
this.player.dispose();
videojs.HlsHandler.prototype.setupQualityLevels_ = ogHlsHandlerSetupQualityLevels;
}
});
@ -4864,20 +4966,40 @@ QUnit.test(
QUnit.module('videojs-contrib-hls isolated functions');
QUnit.test('emeKeySystems adds content types for all keySystems', function(assert) {
// muxed content
assert.deepEqual(
emeKeySystems(
{ keySystem1: {}, keySystem2: {} },
{ attributes: { CODECS: 'some-video-codec' } },
{ attributes: { CODECS: 'some-audio-codec' } }
{ attributes: { CODECS: 'avc1.420015, mp4a.40.2c' } },
),
{
keySystem1: {
audioContentType: 'audio/mp4; codecs="some-audio-codec"',
videoContentType: 'video/mp4; codecs="some-video-codec"'
audioContentType: 'audio/mp4;codecs="mp4a.40.2c"',
videoContentType: 'video/mp4;codecs="avc1.420015"'
},
keySystem2: {
audioContentType: 'audio/mp4; codecs="some-audio-codec"',
videoContentType: 'video/mp4; codecs="some-video-codec"'
audioContentType: 'audio/mp4;codecs="mp4a.40.2c"',
videoContentType: 'video/mp4;codecs="avc1.420015"'
}
},
'added content types'
);
// unmuxed content
assert.deepEqual(
emeKeySystems(
{ keySystem1: {}, keySystem2: {} },
{ attributes: { CODECS: 'avc1.420015' } },
{ attributes: { CODECS: 'mp4a.40.2c' } },
),
{
keySystem1: {
audioContentType: 'audio/mp4;codecs="mp4a.40.2c"',
videoContentType: 'video/mp4;codecs="avc1.420015"'
},
keySystem2: {
audioContentType: 'audio/mp4;codecs="mp4a.40.2c"',
videoContentType: 'video/mp4;codecs="avc1.420015"'
}
},
'added content types'
@ -4888,19 +5010,18 @@ QUnit.test('emeKeySystems retains non content type properties', function(assert)
assert.deepEqual(
emeKeySystems(
{ keySystem1: { url: '1' }, keySystem2: { url: '2'} },
{ attributes: { CODECS: 'some-video-codec' } },
{ attributes: { CODECS: 'some-audio-codec' } }
{ attributes: { CODECS: 'avc1.420015, mp4a.40.2c' } },
),
{
keySystem1: {
url: '1',
audioContentType: 'audio/mp4; codecs="some-audio-codec"',
videoContentType: 'video/mp4; codecs="some-video-codec"'
audioContentType: 'audio/mp4;codecs="mp4a.40.2c"',
videoContentType: 'video/mp4;codecs="avc1.420015"'
},
keySystem2: {
url: '2',
audioContentType: 'audio/mp4; codecs="some-audio-codec"',
videoContentType: 'video/mp4; codecs="some-video-codec"'
audioContentType: 'audio/mp4;codecs="mp4a.40.2c"',
videoContentType: 'video/mp4;codecs="avc1.420015"'
}
},
'retained options'
@ -4920,17 +5041,16 @@ QUnit.test('emeKeySystems overwrites content types', function(assert) {
videoContentType: 'd'
}
},
{ attributes: { CODECS: 'some-video-codec' } },
{ attributes: { CODECS: 'some-audio-codec' } }
{ attributes: { CODECS: 'avc1.420015, mp4a.40.2c' } },
),
{
keySystem1: {
audioContentType: 'audio/mp4; codecs="some-audio-codec"',
videoContentType: 'video/mp4; codecs="some-video-codec"'
audioContentType: 'audio/mp4;codecs="mp4a.40.2c"',
videoContentType: 'video/mp4;codecs="avc1.420015"'
},
keySystem2: {
audioContentType: 'audio/mp4; codecs="some-audio-codec"',
videoContentType: 'video/mp4; codecs="some-video-codec"'
audioContentType: 'audio/mp4;codecs="mp4a.40.2c"',
videoContentType: 'video/mp4;codecs="avc1.420015"'
}
},
'overwrote content types'

Loading…
Cancel
Save