1
0
Fork 0
mirror of https://github.com/jellyfin/jellyfin-web synced 2025-03-30 19:56:21 +00:00

update components

This commit is contained in:
Luke Pulverenti 2016-02-01 12:02:17 -05:00
parent 2a72f0256e
commit 048ba20590
26 changed files with 1377 additions and 136 deletions

View file

@ -15,12 +15,12 @@
}, },
"devDependencies": {}, "devDependencies": {},
"ignore": [], "ignore": [],
"version": "1.0.49", "version": "1.0.54",
"_release": "1.0.49", "_release": "1.0.54",
"_resolution": { "_resolution": {
"type": "version", "type": "version",
"tag": "1.0.49", "tag": "1.0.54",
"commit": "460488b5fd0723a72995cc23f9f8cb5a860bd370" "commit": "a4de1d3f633579c3583d83990577cc252a9187ad"
}, },
"_source": "git://github.com/MediaBrowser/emby-webcomponents.git", "_source": "git://github.com/MediaBrowser/emby-webcomponents.git",
"_target": "~1.0.0", "_target": "~1.0.0",

View file

@ -29,7 +29,7 @@
window.removeEventListener('popstate', onHashChange); window.removeEventListener('popstate', onHashChange);
if (!self.closedByBack) { if (!self.closedByBack && isHistoryEnabled(dlg)) {
var state = history.state || {}; var state = history.state || {};
if (state.dialogId == hash) { if (state.dialogId == hash) {
history.back(); history.back();
@ -60,10 +60,16 @@
removeScrollLockOnClose = true; removeScrollLockOnClose = true;
} }
if (isHistoryEnabled(dlg)) {
historyManager.pushState({ dialogId: hash }, "Dialog", hash); historyManager.pushState({ dialogId: hash }, "Dialog", hash);
window.addEventListener('popstate', onHashChange); window.addEventListener('popstate', onHashChange);
} }
}
function isHistoryEnabled(dlg) {
return dlg.getAttribute('data-history') == 'true';
}
function open(dlg) { function open(dlg) {
@ -76,7 +82,11 @@
function close(dlg) { function close(dlg) {
if (dlg.opened) { if (dlg.opened) {
if (isHistoryEnabled(dlg)) {
history.back(); history.back();
} else {
dlg.close();
}
} }
} }
@ -111,6 +121,10 @@
dlg.setAttribute('data-lockscroll', 'true'); dlg.setAttribute('data-lockscroll', 'true');
} }
if (options.enableHistory !== false) {
dlg.setAttribute('data-history', 'true');
}
// without this safari will scroll the background instead of the dialog contents // without this safari will scroll the background instead of the dialog contents
// but not needed here since this is already on top of an existing dialog // but not needed here since this is already on top of an existing dialog
// but skip it in IE because it's causing the entire browser to hang // but skip it in IE because it's causing the entire browser to hang

View file

@ -1,4 +1,4 @@
define(['paperdialoghelper', 'inputManager', 'connectionManager', 'browser', 'css!./style', 'html!./icons', 'iron-icon-set'], function (paperdialoghelper, inputmanager, connectionManager, browser) { define(['paperdialoghelper', 'inputManager', 'connectionManager', 'browser', 'css!./style', 'html!./icons', 'iron-icon-set', 'paper-fab', 'paper-icon-button'], function (paperdialoghelper, inputmanager, connectionManager, browser) {
return function (options) { return function (options) {
@ -9,7 +9,7 @@ define(['paperdialoghelper', 'inputManager', 'connectionManager', 'browser', 'cs
function createElements(options) { function createElements(options) {
dlg = paperdialoghelper.createDialog({ dlg = paperdialoghelper.createDialog({
exitAnimationDuration: 800, exitAnimationDuration: options.interactive ? 400 : 800,
size: 'fullscreen' size: 'fullscreen'
}); });
@ -22,7 +22,7 @@ define(['paperdialoghelper', 'inputManager', 'connectionManager', 'browser', 'cs
html += '<div>'; html += '<div>';
html += '<div class="slideshowSwiperContainer"><div class="swiper-wrapper"></div></div>'; html += '<div class="slideshowSwiperContainer"><div class="swiper-wrapper"></div></div>';
html += '<paper-icon-button icon="slideshow:arrow-back" class="btnSlideshowExit" tabindex="-1"></paper-icon-button>'; html += '<paper-fab mini icon="slideshow:arrow-back" class="btnSlideshowExit" tabindex="-1"></paper-fab>';
html += '<div class="slideshowControlBar">'; html += '<div class="slideshowControlBar">';
html += '<paper-icon-button icon="slideshow:skip-previous" class="btnSlideshowPrevious slideshowButton"></paper-icon-button>'; html += '<paper-icon-button icon="slideshow:skip-previous" class="btnSlideshowPrevious slideshowButton"></paper-icon-button>';
@ -77,7 +77,7 @@ define(['paperdialoghelper', 'inputManager', 'connectionManager', 'browser', 'cs
swiperInstance = new Swiper(dlg.querySelector('.slideshowSwiperContainer'), { swiperInstance = new Swiper(dlg.querySelector('.slideshowSwiperContainer'), {
// Optional parameters // Optional parameters
direction: 'horizontal', direction: 'horizontal',
loop: true, loop: options.loop !== false,
autoplay: options.interval || 8000, autoplay: options.interval || 8000,
// Disable preloading of all images // Disable preloading of all images
preloadImages: false, preloadImages: false,
@ -142,6 +142,15 @@ define(['paperdialoghelper', 'inputManager', 'connectionManager', 'browser', 'cs
function nextImage() { function nextImage() {
if (swiperInstance) { if (swiperInstance) {
if (options.loop === false) {
if (swiperInstance.activeIndex >= swiperInstance.slides.length - 1) {
paperdialoghelper.close(dlg);
return;
}
}
swiperInstance.slideNext(); swiperInstance.slideNext();
} else { } else {
stopInterval(); stopInterval();

View file

@ -10,6 +10,7 @@
background-repeat: no-repeat; background-repeat: no-repeat;
margin: 0 !important; margin: 0 !important;
color: #fff; color: #fff;
line-height: normal;
} }
.slideshowImage.cover { .slideshowImage.cover {
@ -46,13 +47,17 @@
.btnSlideshowExit { .btnSlideshowExit {
z-index: 1002; z-index: 1002;
position: absolute; position: absolute;
top: 1%; top: 1.5vh;
left: .5%; left: 1.5vh;
width: 6vh; width: 6vh;
height: 6vh; height: 6vh;
color: #eee; color: #eee;
} }
paper-fab.btnSlideshowExit {
background-color: #444;
}
.slideshowControlBar { .slideshowControlBar {
position: fixed; position: fixed;
left: 0; left: 0;

View file

@ -1,6 +1,6 @@
{ {
"name": "hls.js", "name": "hls.js",
"version": "0.4.7", "version": "0.4.8",
"description": "Media Source Extension - HLS library, by/for Dailymotion", "description": "Media Source Extension - HLS library, by/for Dailymotion",
"homepage": "https://github.com/dailymotion/hls.js", "homepage": "https://github.com/dailymotion/hls.js",
"authors": [ "authors": [
@ -15,11 +15,11 @@
"test", "test",
"tests" "tests"
], ],
"_release": "0.4.7", "_release": "0.4.8",
"_resolution": { "_resolution": {
"type": "version", "type": "version",
"tag": "v0.4.7", "tag": "v0.4.8",
"commit": "9c1a7e7d768346dcc4012e8b80f73e823af5ff20" "commit": "de1534ca184ffa3a313fc5b433713512f6ab1c27"
}, },
"_source": "git://github.com/dailymotion/hls.js.git", "_source": "git://github.com/dailymotion/hls.js.git",
"_target": "~0.4.5", "_target": "~0.4.5",

View file

@ -209,7 +209,9 @@ configuration parameters could be provided to hls.js upon instantiation of Hls O
fLoader: customFragmentLoader, fLoader: customFragmentLoader,
pLoader: customPlaylistLoader, pLoader: customPlaylistLoader,
xhrSetup : XMLHttpRequestSetupCallback, xhrSetup : XMLHttpRequestSetupCallback,
abrController : customAbrController abrController : customAbrController,
timelineController: TimelineController,
enableCEA708Captions: true
}; };
@ -403,6 +405,21 @@ parameter should be a class providing 2 getter/setters and a destroy() method:
- get/set autoLevelCapping : get/set : capping/max level value that could be used by ABR Controller - get/set autoLevelCapping : get/set : capping/max level value that could be used by ABR Controller
- destroy() : should clean-up all used resources - destroy() : should clean-up all used resources
#### ```timelineController```
(default : internal track timeline controller)
customized text track syncronization controller
parameter should be a class a destroy() method:
- destroy() : should clean-up all used resources
#### ```enableCEA708Captions```
(default : true)
whether or not to enable CEA-708 captions
parameter should be a boolean
## Video Binding/Unbinding API ## Video Binding/Unbinding API

View file

@ -1,6 +1,6 @@
{ {
"name": "hls.js", "name": "hls.js",
"version": "0.4.6", "version": "0.4.8",
"description": "Media Source Extension - HLS library, by/for Dailymotion", "description": "Media Source Extension - HLS library, by/for Dailymotion",
"homepage": "https://github.com/dailymotion/hls.js", "homepage": "https://github.com/dailymotion/hls.js",
"authors": [ "authors": [

View file

@ -225,7 +225,6 @@ $(document).ready(function() {
loadStream(decodeURIComponent(getURLParam('src','http://www.streambox.fr/playlists/x36xhzz/x36xhzz.m3u8'))); loadStream(decodeURIComponent(getURLParam('src','http://www.streambox.fr/playlists/x36xhzz/x36xhzz.m3u8')));
function loadStream(url) { function loadStream(url) {
hideCanvas(); hideCanvas();
if(Hls.isSupported()) { if(Hls.isSupported()) {

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,6 +1,6 @@
{ {
"name": "hls.js", "name": "hls.js",
"version": "0.4.6", "version": "0.4.8",
"description": "Media Source Extension - HLS library, by/for Dailymotion", "description": "Media Source Extension - HLS library, by/for Dailymotion",
"homepage": "https://github.com/dailymotion/hls.js", "homepage": "https://github.com/dailymotion/hls.js",
"authors": "Guillaume du Pontavice <guillaume.dupontavice@dailymotion.com>", "authors": "Guillaume du Pontavice <guillaume.dupontavice@dailymotion.com>",

View file

@ -60,11 +60,12 @@ class LevelController extends EventHandler {
// only keep level with supported audio/video codecs // only keep level with supported audio/video codecs
levels = levels.filter(function(level) { levels = levels.filter(function(level) {
var checkSupported = function(codec) { return MediaSource.isTypeSupported(`video/mp4;codecs=${codec}`);}; var checkSupportedAudio = function(codec) { return MediaSource.isTypeSupported(`audio/mp4;codecs=${codec}`);};
var checkSupportedVideo = function(codec) { return MediaSource.isTypeSupported(`video/mp4;codecs=${codec}`);};
var audioCodec = level.audioCodec, videoCodec = level.videoCodec; var audioCodec = level.audioCodec, videoCodec = level.videoCodec;
return (!audioCodec || checkSupported(audioCodec)) && return (!audioCodec || checkSupportedAudio(audioCodec)) &&
(!videoCodec || checkSupported(videoCodec)); (!videoCodec || checkSupportedVideo(videoCodec));
}); });
if(levels.length) { if(levels.length) {

View file

@ -1076,7 +1076,7 @@ class MSEMediaController extends EventHandler {
logger.log(`selected A/V codecs for sourceBuffers:${audioCodec},${videoCodec}`); logger.log(`selected A/V codecs for sourceBuffers:${audioCodec},${videoCodec}`);
// create source Buffer and link them to MediaSource // create source Buffer and link them to MediaSource
if (audioCodec) { if (audioCodec) {
sb = this.sourceBuffer.audio = this.mediaSource.addSourceBuffer(`video/mp4;codecs=${audioCodec}`); sb = this.sourceBuffer.audio = this.mediaSource.addSourceBuffer(`audio/mp4;codecs=${audioCodec}`);
sb.addEventListener('updateend', this.onsbue); sb.addEventListener('updateend', this.onsbue);
sb.addEventListener('error', this.onsbe); sb.addEventListener('error', this.onsbe);
} }

View file

@ -0,0 +1,69 @@
/*
* Timeline Controller
*/
import Event from '../events';
import EventHandler from '../event-handler';
import CEA708Interpreter from '../utils/cea-708-interpreter';
class TimelineController extends EventHandler {
constructor(hls) {
super(hls, Event.MEDIA_ATTACHING,
Event.MEDIA_DETACHING,
Event.FRAG_PARSING_USERDATA,
Event.MANIFEST_LOADING,
Event.FRAG_LOADED);
this.hls = hls;
this.config = hls.config;
if (this.config.enableCEA708Captions)
{
this.cea708Interpreter = new CEA708Interpreter();
}
}
destroy() {
EventHandler.prototype.destroy.call(this);
}
onMediaAttaching(data) {
var media = this.media = data.media;
this.cea708Interpreter.attach(media);
}
onMediaDetaching() {
this.cea708Interpreter.detach();
}
onManifestLoading()
{
this.lastPts = Number.POSITIVE_INFINITY;
}
onFragLoaded(data)
{
var pts = data.frag.start; //Number.POSITIVE_INFINITY;
// if this is a frag for a previously loaded timerange, remove all captions
// TODO: consider just removing captions for the timerange
if (pts <= this.lastPts)
{
this.cea708Interpreter.clear();
}
this.lastPts = pts;
}
onFragParsingUserdata(data) {
// push all of the CEA-708 messages into the interpreter
// immediately. It will create the proper timestamps based on our PTS value
for (var i=0; i<data.samples.length; i++)
{
this.cea708Interpreter.push(data.samples[i].pts, data.samples[i].bytes);
}
}
}
export default TimelineController;

View file

@ -72,6 +72,12 @@ var DemuxerWorker = function (self) {
var objData = {event: event, samples: data.samples}; var objData = {event: event, samples: data.samples};
self.postMessage(objData); self.postMessage(objData);
}); });
observer.on(Event.FRAG_PARSING_USERDATA, function(event, data) {
var objData = {event: event, samples: data.samples};
self.postMessage(objData);
});
}; };
export default DemuxerWorker; export default DemuxerWorker;

View file

@ -101,6 +101,11 @@ class Demuxer {
samples: ev.data.samples samples: ev.data.samples
}); });
break; break;
case Event.FRAG_PARSING_USERDATA:
this.hls.trigger(Event.FRAG_PARSING_USERDATA, {
samples: ev.data.samples
});
break;
default: default:
this.hls.trigger(ev.data.event, ev.data.data); this.hls.trigger(ev.data.event, ev.data.data);
break; break;

View file

@ -125,6 +125,15 @@ class ExpGolomb {
return this.readBits(8); return this.readBits(8);
} }
// ():int
readUShort() {
return this.readBits(16);
}
// ():int
readUInt() {
return this.readBits(32);
}
/** /**
* Advance the ExpGolomb decoder past a scaling list. The scaling * Advance the ExpGolomb decoder past a scaling list. The scaling
* list is optionally transmitted as part of a sequence parameter * list is optionally transmitted as part of a sequence parameter

View file

@ -23,6 +23,7 @@
this.remuxerClass = remuxerClass; this.remuxerClass = remuxerClass;
this.lastCC = 0; this.lastCC = 0;
this.remuxer = new this.remuxerClass(observer); this.remuxer = new this.remuxerClass(observer);
this._userData = [];
} }
static probe(data) { static probe(data) {
@ -42,6 +43,7 @@
this._avcTrack = {type: 'video', id :-1, sequenceNumber: 0, samples : [], len : 0, nbNalu : 0}; this._avcTrack = {type: 'video', id :-1, sequenceNumber: 0, samples : [], len : 0, nbNalu : 0};
this._aacTrack = {type: 'audio', id :-1, sequenceNumber: 0, samples : [], len : 0}; this._aacTrack = {type: 'audio', id :-1, sequenceNumber: 0, samples : [], len : 0};
this._id3Track = {type: 'id3', id :-1, sequenceNumber: 0, samples : [], len : 0}; this._id3Track = {type: 'id3', id :-1, sequenceNumber: 0, samples : [], len : 0};
this._txtTrack = {type: 'text', id: -1, sequenceNumber: 0, samples: [], len: 0};
this.remuxer.switchLevel(); this.remuxer.switchLevel();
} }
@ -81,6 +83,9 @@
avcId = this._avcTrack.id, avcId = this._avcTrack.id,
aacId = this._aacTrack.id, aacId = this._aacTrack.id,
id3Id = this._id3Track.id; id3Id = this._id3Track.id;
// don't parse last TS packet if incomplete
len -= len % 188;
// loop through TS packets // loop through TS packets
for (start = 0; start < len; start += 188) { for (start = 0; start < len; start += 188) {
if (data[start] === 0x47) { if (data[start] === 0x47) {
@ -165,7 +170,7 @@
} }
remux() { remux() {
this.remuxer.remux(this._aacTrack,this._avcTrack, this._id3Track, this.timeOffset, this.contiguous); this.remuxer.remux(this._aacTrack, this._avcTrack, this._id3Track, this._txtTrack, this.timeOffset, this.contiguous);
} }
destroy() { destroy() {
@ -281,8 +286,10 @@
debug = false, debug = false,
key = false, key = false,
length = 0, length = 0,
expGolombDecoder,
avcSample, avcSample,
push; push,
i;
// no NALu found // no NALu found
if (units.length === 0 && samples.length > 0) { if (units.length === 0 && samples.length > 0) {
// append pes.data to previous NAL unit // append pes.data to previous NAL unit
@ -298,6 +305,7 @@
//free pes.data to save up some memory //free pes.data to save up some memory
pes.data = null; pes.data = null;
var debugString = ''; var debugString = '';
units.forEach(unit => { units.forEach(unit => {
switch(unit.type) { switch(unit.type) {
//NDR //NDR
@ -315,11 +323,67 @@
} }
key = true; key = true;
break; break;
//SEI
case 6: case 6:
push = true; push = true;
if(debug) { if(debug) {
debugString += 'SEI '; debugString += 'SEI ';
} }
expGolombDecoder = new ExpGolomb(unit.data);
// skip frameType
expGolombDecoder.readUByte();
var payloadType = expGolombDecoder.readUByte();
// TODO: there can be more than one payload in an SEI packet...
// TODO: need to read type and size in a while loop to get them all
if (payloadType === 4)
{
var payloadSize = 0;
do {
payloadSize = expGolombDecoder.readUByte();
}
while (payloadSize === 255);
var countryCode = expGolombDecoder.readUByte();
if (countryCode === 181)
{
var providerCode = expGolombDecoder.readUShort();
if (providerCode === 49)
{
var userStructure = expGolombDecoder.readUInt();
if (userStructure === 0x47413934)
{
var userDataType = expGolombDecoder.readUByte();
// Raw CEA-608 bytes wrapped in CEA-708 packet
if (userDataType === 3)
{
var firstByte = expGolombDecoder.readUByte();
var secondByte = expGolombDecoder.readUByte();
var totalCCs = 31 & firstByte;
var byteArray = [firstByte, secondByte];
for (i=0; i<totalCCs; i++)
{
// 3 bytes per CC
byteArray.push(expGolombDecoder.readUByte());
byteArray.push(expGolombDecoder.readUByte());
byteArray.push(expGolombDecoder.readUByte());
}
this._txtTrack.samples.push({type: 3, pts: pes.pts, bytes: byteArray});
}
}
}
}
}
break; break;
//SPS //SPS
case 7: case 7:
@ -328,7 +392,7 @@
debugString += 'SPS '; debugString += 'SPS ';
} }
if(!track.sps) { if(!track.sps) {
var expGolombDecoder = new ExpGolomb(unit.data); expGolombDecoder = new ExpGolomb(unit.data);
var config = expGolombDecoder.readSPS(); var config = expGolombDecoder.readSPS();
track.width = config.width; track.width = config.width;
track.height = config.height; track.height = config.height;
@ -337,7 +401,7 @@
track.duration = this.remuxer.timescale * this._duration; track.duration = this.remuxer.timescale * this._duration;
var codecarray = unit.data.subarray(1, 4); var codecarray = unit.data.subarray(1, 4);
var codecstring = 'avc1.'; var codecstring = 'avc1.';
for (var i = 0; i < 3; i++) { for (i = 0; i < 3; i++) {
var h = codecarray[i].toString(16); var h = codecarray[i].toString(16);
if (h.length < 2) { if (h.length < 2) {
h = '0' + h; h = '0' + h;
@ -358,7 +422,7 @@
} }
break; break;
case 9: case 9:
push = true; push = false;
if(debug) { if(debug) {
debugString += 'AUD '; debugString += 'AUD ';
} }
@ -443,10 +507,6 @@
} }
lastUnitStart = i; lastUnitStart = i;
lastUnitType = unitType; lastUnitType = unitType;
if (unitType === 1 || unitType === 5) {
// OPTI !!! if IDR/NDR unit, consider it is last NALu
i = len;
}
state = 0; state = 0;
} else { } else {
state = 0; state = 0;

View file

@ -33,6 +33,8 @@ module.exports = {
FRAG_LOADED: 'hlsFragLoaded', FRAG_LOADED: 'hlsFragLoaded',
// fired when Init Segment has been extracted from fragment - data: { moov : moov MP4 box, codecs : codecs found while parsing fragment} // fired when Init Segment has been extracted from fragment - data: { moov : moov MP4 box, codecs : codecs found while parsing fragment}
FRAG_PARSING_INIT_SEGMENT: 'hlsFragParsingInitSegment', FRAG_PARSING_INIT_SEGMENT: 'hlsFragParsingInitSegment',
// fired when parsing sei text is completed - data: { samples : [ sei samples pes ] }
FRAG_PARSING_USERDATA: 'hlsFragParsingUserdata',
// fired when parsing id3 is completed - data: { samples : [ id3 samples pes ] } // fired when parsing id3 is completed - data: { samples : [ id3 samples pes ] }
FRAG_PARSING_METADATA: 'hlsFragParsingMetadata', FRAG_PARSING_METADATA: 'hlsFragParsingMetadata',
// fired when moof/mdat have been extracted from fragment - data: { moof : moof MP4 box, mdat : mdat MP4 box} // fired when moof/mdat have been extracted from fragment - data: { moof : moof MP4 box, mdat : mdat MP4 box}

View file

@ -10,6 +10,7 @@ import FragmentLoader from './loader/fragment-loader';
import AbrController from './controller/abr-controller'; import AbrController from './controller/abr-controller';
import MSEMediaController from './controller/mse-media-controller'; import MSEMediaController from './controller/mse-media-controller';
import LevelController from './controller/level-controller'; import LevelController from './controller/level-controller';
import TimelineController from './controller/timeline-controller';
//import FPSController from './controller/fps-controller'; //import FPSController from './controller/fps-controller';
import {logger, enableLogs} from './utils/logger'; import {logger, enableLogs} from './utils/logger';
import XhrLoader from './utils/xhr-loader'; import XhrLoader from './utils/xhr-loader';
@ -65,7 +66,9 @@ class Hls {
fLoader: undefined, fLoader: undefined,
pLoader: undefined, pLoader: undefined,
abrController : AbrController, abrController : AbrController,
mediaController: MSEMediaController mediaController: MSEMediaController,
timelineController: TimelineController,
enableCEA708Captions: true
}; };
} }
return Hls.defaultConfig; return Hls.defaultConfig;
@ -105,6 +108,7 @@ class Hls {
this.levelController = new LevelController(this); this.levelController = new LevelController(this);
this.abrController = new config.abrController(this); this.abrController = new config.abrController(this);
this.mediaController = new config.mediaController(this); this.mediaController = new config.mediaController(this);
this.timelineController = new config.timelineController(this);
this.keyLoader = new KeyLoader(this); this.keyLoader = new KeyLoader(this);
//this.fpsController = new FPSController(this); //this.fpsController = new FPSController(this);
} }
@ -117,6 +121,7 @@ class Hls {
this.fragmentLoader.destroy(); this.fragmentLoader.destroy();
this.levelController.destroy(); this.levelController.destroy();
this.mediaController.destroy(); this.mediaController.destroy();
this.timelineController.destroy();
this.keyLoader.destroy(); this.keyLoader.destroy();
//this.fpsController.destroy(); //this.fpsController.destroy();
this.url = null; this.url = null;

View file

@ -18,10 +18,11 @@ class DummyRemuxer {
insertDiscontinuity() { insertDiscontinuity() {
} }
remux(audioTrack,videoTrack,id3Track,timeOffset) { remux(audioTrack,videoTrack,id3Track,textTrack,timeOffset) {
this._remuxAACSamples(audioTrack,timeOffset); this._remuxAACSamples(audioTrack,timeOffset);
this._remuxAVCSamples(videoTrack,timeOffset); this._remuxAVCSamples(videoTrack,timeOffset);
this._remuxID3Samples(id3Track,timeOffset); this._remuxID3Samples(id3Track,timeOffset);
this._remuxTextSamples(textTrack,timeOffset);
} }
_remuxAVCSamples(track, timeOffset) { _remuxAVCSamples(track, timeOffset) {
@ -59,6 +60,17 @@ class DummyRemuxer {
//please lint //please lint
timeOffset = timeOffset; timeOffset = timeOffset;
} }
_remuxTextSamples(track,timeOffset) {
var textSample,bytes;
// loop through track.samples
while (track.samples.length) {
textSample = track.samples.shift();
bytes = textSample.bytes;
}
//please lint
timeOffset = timeOffset;
}
} }
export default DummyRemuxer; export default DummyRemuxer;

View file

@ -355,12 +355,12 @@ class MP4 {
0x00, 0x48, 0x00, 0x00, // vertresolution 0x00, 0x48, 0x00, 0x00, // vertresolution
0x00, 0x00, 0x00, 0x00, // reserved 0x00, 0x00, 0x00, 0x00, // reserved
0x00, 0x01, // frame_count 0x00, 0x01, // frame_count
0x13, 0x12,
0x76, 0x69, 0x64, 0x65, 0x64, 0x61, 0x69, 0x6C, //dailymotion/hls.js
0x6f, 0x6a, 0x73, 0x2d, 0x79, 0x6D, 0x6F, 0x74,
0x63, 0x6f, 0x6e, 0x74, 0x69, 0x6F, 0x6E, 0x2F,
0x72, 0x69, 0x62, 0x2d, 0x68, 0x6C, 0x73, 0x2E,
0x68, 0x6c, 0x73, 0x00, 0x6A, 0x73, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, // compressorname 0x00, 0x00, 0x00, // compressorname

View file

@ -32,7 +32,7 @@ class MP4Remuxer {
this.ISGenerated = false; this.ISGenerated = false;
} }
remux(audioTrack,videoTrack,id3Track,timeOffset, contiguous) { remux(audioTrack,videoTrack,id3Track,textTrack,timeOffset, contiguous) {
// generate Init Segment if needed // generate Init Segment if needed
if (!this.ISGenerated) { if (!this.ISGenerated) {
this.generateIS(audioTrack,videoTrack,timeOffset); this.generateIS(audioTrack,videoTrack,timeOffset);
@ -49,6 +49,10 @@ class MP4Remuxer {
if (id3Track.samples.length) { if (id3Track.samples.length) {
this.remuxID3(id3Track,timeOffset); this.remuxID3(id3Track,timeOffset);
} }
//logger.log('nb ID3 samples:' + audioTrack.samples.length);
if (textTrack.samples.length) {
this.remuxText(textTrack,timeOffset);
}
//notify end of parsing //notify end of parsing
this.observer.trigger(Event.FRAG_PARSED); this.observer.trigger(Event.FRAG_PARSED);
} }
@ -264,6 +268,7 @@ class MP4Remuxer {
pts = aacSample.pts; pts = aacSample.pts;
} else { } else {
logger.warn('dropping past audio frame'); logger.warn('dropping past audio frame');
track.len -= aacSample.unit.byteLength;
} }
}); });
@ -309,12 +314,17 @@ class MP4Remuxer {
// remember first PTS of our aacSamples, ensure value is positive // remember first PTS of our aacSamples, ensure value is positive
firstPTS = Math.max(0, ptsnorm); firstPTS = Math.max(0, ptsnorm);
firstDTS = Math.max(0, dtsnorm); firstDTS = Math.max(0, dtsnorm);
if(track.len > 0) {
/* concatenate the audio data and construct the mdat in place /* concatenate the audio data and construct the mdat in place
(need 8 more bytes to fill length and mdat type) */ (need 8 more bytes to fill length and mdat type) */
mdat = new Uint8Array(track.len + 8); mdat = new Uint8Array(track.len + 8);
view = new DataView(mdat.buffer); view = new DataView(mdat.buffer);
view.setUint32(0, mdat.byteLength); view.setUint32(0, mdat.byteLength);
mdat.set(MP4.types.mdat, 4); mdat.set(MP4.types.mdat, 4);
} else {
// no audio samples
return;
}
} }
mdat.set(unit, offset); mdat.set(unit, offset);
offset += unit.byteLength; offset += unit.byteLength;
@ -382,6 +392,29 @@ class MP4Remuxer {
timeOffset = timeOffset; timeOffset = timeOffset;
} }
remuxText(track,timeOffset) {
track.samples.sort(function(a, b) {
return (a.pts-b.pts);
});
var length = track.samples.length, sample;
// consume samples
if(length) {
for(var index = 0; index < length; index++) {
sample = track.samples[index];
// setting text pts, dts to relative time
// using this._initPTS and this._initDTS to calculate relative time
sample.pts = ((sample.pts - this._initPTS) / this.PES_TIMESCALE);
}
this.observer.trigger(Event.FRAG_PARSING_USERDATA, {
samples:track.samples
});
}
track.samples = [];
timeOffset = timeOffset;
}
_PTSNormalize(value, reference) { _PTSNormalize(value, reference) {
var offset; var offset;
if (reference === undefined) { if (reference === undefined) {

View file

@ -0,0 +1,380 @@
/*
* CEA-708 interpreter
*/
class CEA708Interpreter {
constructor() {
}
attach(media) {
this.media = media;
this.display = [];
this.memory = [];
}
detach()
{
this.clear();
}
destroy() {
}
_createCue()
{
var VTTCue = window.VTTCue || window.TextTrackCue;
this.cue = new VTTCue(-1, -1, '');
this.cue.text = '';
this.cue.pauseOnExit = false;
// make sure it doesn't show up before it's ready
this.startTime = Number.MAX_VALUE;
// show it 'forever' once we do show it
// (we'll set the end time once we know it later)
this.cue.endTime = Number.MAX_VALUE;
this.memory.push(this.cue);
}
clear()
{
if (this._textTrack && this._textTrack.cues)
{
while (this._textTrack.cues.length > 0)
{
this._textTrack.removeCue(this._textTrack.cues[0]);
}
}
}
push(timestamp, bytes)
{
if (!this.cue)
{
this._createCue();
}
var count = bytes[0] & 31;
var position = 2;
var byte, ccbyte1, ccbyte2, ccValid, ccType;
for (var j=0; j<count; j++)
{
byte = bytes[position++];
ccbyte1 = 0x7F & bytes[position++];
ccbyte2 = 0x7F & bytes[position++];
ccValid = ((4 & byte) === 0 ? false : true);
ccType = (3 & byte);
if (ccbyte1 === 0 && ccbyte2 === 0)
{
continue;
}
if (ccValid)
{
if (ccType === 0) // || ccType === 1
{
// Standard Characters
if (0x20 & ccbyte1 || 0x40 & ccbyte1)
{
this.cue.text += this._fromCharCode(ccbyte1) + this._fromCharCode(ccbyte2);
}
// Special Characters
else if ((ccbyte1 === 0x11 || ccbyte1 === 0x19) && ccbyte2 >= 0x30 && ccbyte2 <= 0x3F)
{
// extended chars, e.g. musical note, accents
switch (ccbyte2)
{
case 48:
this.cue.text += '®';
break;
case 49:
this.cue.text += '°';
break;
case 50:
this.cue.text += '½';
break;
case 51:
this.cue.text += '¿';
break;
case 52:
this.cue.text += '™';
break;
case 53:
this.cue.text += '¢';
break;
case 54:
this.cue.text += '';
break;
case 55:
this.cue.text += '£';
break;
case 56:
this.cue.text += '♪';
break;
case 57:
this.cue.text += ' ';
break;
case 58:
this.cue.text += 'è';
break;
case 59:
this.cue.text += 'â';
break;
case 60:
this.cue.text += 'ê';
break;
case 61:
this.cue.text += 'î';
break;
case 62:
this.cue.text += 'ô';
break;
case 63:
this.cue.text += 'û';
break;
}
}
if ((ccbyte1 === 0x11 || ccbyte1 === 0x19) && ccbyte2 >= 0x20 && ccbyte2 <= 0x2F)
{
// Mid-row codes: color/underline
switch (ccbyte2)
{
case 0x20:
// White
break;
case 0x21:
// White Underline
break;
case 0x22:
// Green
break;
case 0x23:
// Green Underline
break;
case 0x24:
// Blue
break;
case 0x25:
// Blue Underline
break;
case 0x26:
// Cyan
break;
case 0x27:
// Cyan Underline
break;
case 0x28:
// Red
break;
case 0x29:
// Red Underline
break;
case 0x2A:
// Yellow
break;
case 0x2B:
// Yellow Underline
break;
case 0x2C:
// Magenta
break;
case 0x2D:
// Magenta Underline
break;
case 0x2E:
// Italics
break;
case 0x2F:
// Italics Underline
break;
}
}
if ((ccbyte1 === 0x14 || ccbyte1 === 0x1C) && ccbyte2 >= 0x20 && ccbyte2 <= 0x2F)
{
// Mid-row codes: color/underline
switch (ccbyte2)
{
case 0x20:
// TODO: shouldn't affect roll-ups...
this._clearActiveCues(timestamp);
// RCL: Resume Caption Loading
// begin pop on
break;
case 0x21:
// BS: Backspace
this.cue.text = this.cue.text.substr(0, this.cue.text.length-1);
break;
case 0x22:
// AOF: reserved (formerly alarm off)
break;
case 0x23:
// AON: reserved (formerly alarm on)
break;
case 0x24:
// DER: Delete to end of row
break;
case 0x25:
// RU2: roll-up 2 rows
//this._rollup(2);
break;
case 0x26:
// RU3: roll-up 3 rows
//this._rollup(3);
break;
case 0x27:
// RU4: roll-up 4 rows
//this._rollup(4);
break;
case 0x28:
// FON: Flash on
break;
case 0x29:
// RDC: Resume direct captioning
this._clearActiveCues(timestamp);
break;
case 0x2A:
// TR: Text Restart
break;
case 0x2B:
// RTD: Resume Text Display
break;
case 0x2C:
// EDM: Erase Displayed Memory
this._clearActiveCues(timestamp);
break;
case 0x2D:
// CR: Carriage Return
// only affects roll-up
//this._rollup(1);
break;
case 0x2E:
// ENM: Erase non-displayed memory
this._text = '';
break;
case 0x2F:
this._flipMemory(timestamp);
// EOC: End of caption
// hide any displayed captions and show any hidden one
break;
}
}
if ((ccbyte1 === 0x17 || ccbyte1 === 0x1F) && ccbyte2 >= 0x21 && ccbyte2 <= 0x23)
{
// Mid-row codes: color/underline
switch (ccbyte2)
{
case 0x21:
// TO1: tab offset 1 column
break;
case 0x22:
// TO1: tab offset 2 column
break;
case 0x23:
// TO1: tab offset 3 column
break;
}
}
else {
// Probably a pre-amble address code
}
}
}
}
}
_fromCharCode(byte)
{
switch (byte)
{
case 42:
return 'á';
case 2:
return 'á';
case 2:
return 'é';
case 4:
return 'í';
case 5:
return 'ó';
case 6:
return 'ú';
case 3:
return 'ç';
case 4:
return '÷';
case 5:
return 'Ñ';
case 6:
return 'ñ';
case 7:
return '█';
default:
return String.fromCharCode(byte);
}
}
_flipMemory(timestamp)
{
this._clearActiveCues(timestamp);
this._flushCaptions(timestamp);
}
_flushCaptions(timestamp)
{
if (!this._has708)
{
this._textTrack = this.media.addTextTrack('captions', 'English', 'en');
this._has708 = true;
}
for (var i=0; i<this.memory.length; i++)
{
this.memory[i].startTime = timestamp;
this._textTrack.addCue(this.memory[i]);
this.display.push(this.memory[i]);
}
this.memory = [];
this.cue = null;
}
_clearActiveCues(timestamp)
{
for (var i=0; i<this.display.length; i++)
{
this.display[i].endTime = timestamp;
}
this.display = [];
}
/* _rollUp(n)
{
// TODO: implement roll-up captions
}
*/
_clearBufferedCues()
{
//remove them all...
}
}
export default CEA708Interpreter;

View file

@ -46,8 +46,7 @@ var URLHelper = {
builtURL = baseURLDomain+URLHelper.buildAbsolutePath('', relativeURL.substring(1)); builtURL = baseURLDomain+URLHelper.buildAbsolutePath('', relativeURL.substring(1));
} }
else { else {
var newPath = URLHelper.buildAbsolutePath(baseURLPath, relativeURL); builtURL = URLHelper.buildAbsolutePath(baseURLDomain+baseURLPath, relativeURL);
builtURL = baseURLDomain + newPath;
} }
// put the query and hash parts back // put the query and hash parts back