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

update login

This commit is contained in:
Luke Pulverenti 2016-02-24 22:15:07 -05:00
parent 3c9e6e0374
commit 08122a5e93
36 changed files with 2845 additions and 2228 deletions

View file

@ -1,6 +1,6 @@
{
"name": "hls.js",
"version": "0.4.10",
"version": "0.5.7",
"license": "Apache-2.0",
"description": "Media Source Extension - HLS library, by/for Dailymotion",
"homepage": "https://github.com/dailymotion/hls.js",
@ -16,13 +16,14 @@
"test",
"tests"
],
"_release": "0.4.10",
"_release": "0.5.7",
"_resolution": {
"type": "version",
"tag": "v0.4.10",
"commit": "57c46a5eaa157dba3fc495236ff30a61a3ea5d89"
"tag": "v0.5.7",
"commit": "448de529aa48dd7e32d639f265995c51f6dd22f5"
},
"_source": "git://github.com/dailymotion/hls.js.git",
"_target": "~0.4.5",
"_originalSource": "dailymotion/hls.js"
"_target": "~0.5.7",
"_originalSource": "dailymotion/hls.js",
"_direct": true
}

View file

@ -182,8 +182,8 @@ configuration parameters could be provided to hls.js upon instantiation of Hls O
```js
var config = {
debug : false,
autoStartLoad : true,
debug : false,
defaultAudioCodec : undefined,
maxBufferLength : 30,
maxMaxBufferLength : 600,
@ -203,8 +203,7 @@ configuration parameters could be provided to hls.js upon instantiation of Hls O
fragLoadingTimeOut : 20000,
fragLoadingMaxRetry : 6,
fragLoadingRetryDelay : 500,
fpsDroppedMonitoringPeriod : 5000,
fpsDroppedMonitoringThreshold : 0.2,
startFragPrefech : false,
appendErrorMaxRetry : 3,
loader : customLoader,
fLoader: customFragmentLoader,
@ -323,6 +322,11 @@ max nb of load retry
initial delay between XmlHttpRequest error and first load retry (in ms)
any I/O error will trigger retries every 500ms,1s,2s,4s,8s, ... capped to 64s (exponential backoff)
prefetch start fragment although media not attached
#### ```startFragPrefetch```
(default false)
start prefetching start fragment although media not attached yet
max nb of append retry
#### ```appendErrorMaxRetry```

View file

@ -1,7 +1,7 @@
{
"name": "hls.js",
"version": "0.4.10",
"license" : "Apache-2.0",
"version": "0.5.7",
"license": "Apache-2.0",
"description": "Media Source Extension - HLS library, by/for Dailymotion",
"homepage": "https://github.com/dailymotion/hls.js",
"authors": [

View file

@ -13,22 +13,32 @@ design idea is pretty simple :
- definition of Hls.Events
- [src/errors.js][]
- definition of Hls.ErrorTypes and Hls.ErrorDetails
- [src/controller/mse-media-controller.js][]
- [src/controller/stream-controller.js][]
- in charge of:
- ensuring that buffer is filled as per defined quality selection logic.
- monitoring current playback quality level (buffer controller maintains a map between media position and quality level)
- if buffer is not filled up appropriately (i.e. as per defined maximum buffer size, or as per defined quality level), buffer controller will trigger the following actions:
- retrieve "not buffered" media position greater then current playback position. this is performed by comparing video.buffered and video.currentTime.
- retrieve URL of fragment matching with this media position, and appropriate quality level
- trigger fragment loading
- monitor fragment loading speed:
- trigger FRAG_LOADING event
- monitor fragment loading speed (by monitoring data received from FRAG_LOAD_PROGRESS event)
- "expected time of fragment load completion" is computed using "fragment loading instant bandwidth".
- this time is compared to the "expected time of buffer starvation".
- if we have less than 2 fragments buffered and if "expected time of fragment load completion" is bigger than "expected time of buffer starvation" and also bigger than duration needed to load fragment at next quality level (determined by auto quality switch algorithm), current fragment loading is aborted, and an emergency switch down is triggered.
- trigger fragment parsing (TS demuxing and remuxing in MP4 boxes) upon loading completion
- trigger MP4 boxes appending in [SourceBuffer](http://www.w3.org/TR/media-source/#sourcebuffer) upon fragment parsing completion.
buffer controller actions are scheduled by a tick timer (invoked every 100ms) and actions are controlled by a state machine.
- trigger fragment demuxing on FRAG_LOADED
- trigger BUFFER_RESET on MANIFEST_PARSED or startLoad()
- trigger BUFFER_CODECS on FRAG_PARSING_INIT_SEGMENT
- trigger BUFFER_APPENDING on FRAG_PARSING_DATA
- once FRAG_PARSED is received an all segments have been appended (BUFFER_APPENDED) then buffer controller will recheck whether it needs to buffer more data.
- monitor current playback quality level (buffer controller maintains a map between media position and quality level)
stream controller actions are scheduled by a tick timer (invoked every 100ms) and actions are controlled by a state machine.
- [src/controller/buffer-controller.js][]
- in charge of:
- resetting media buffer upon BUFFER_RESET event reception
- initializing [SourceBuffer](http://www.w3.org/TR/media-source/#sourcebuffer) with appropriate codecs info upon BUFFER_CODECS event reception
- appending MP4 boxes in [SourceBuffer](http://www.w3.org/TR/media-source/#sourcebuffer) upon BUFFER_APPENDING
- trigger BUFFER_APPENDED event upon successful buffer appending
- flushing specified buffer range upon reception of BUFFER_FLUSHING event
- trigger BUFFER_FLUSHED event upon successful buffer flushing
- [src/controller/fps-controller.js][]
- in charge of monitoring frame rate, and fire FPS_DROP event in case FPS drop exceeds configured threshold. disabled for now.
@ -117,9 +127,10 @@ design idea is pretty simple :
[src/errors.js]: src/errors.js
[src/stats.js]: src/stats.js
[src/controller/abr-controller.js]: src/controller/abr-controller.js
[src/controller/buffer-controller.js]: src/controller/buffer-controller.js
[src/controller/fps-controller.js]: src/controller/fps-controller.js
[src/controller/level-controller.js]: src/controller/level-controller.js
[src/controller/mse-media-controller.js]: src/controller/mse-media-controller.js
[src/controller/stream-controller.js]: src/controller/stream-controller.js
[src/crypt/aes.js]: src/crypt/aes.js
[src/crypt/aes128-decrypter.js]: src/crypt/aes128-decrypter.js
[src/crypt/decrypter.js]: src/crypt/decrypter.js
@ -157,7 +168,7 @@ design idea is pretty simple :
- ```FRAG_LOAD_ERROR``` is raised by [src/loader/fragment-loader.js][] upon xhr failure detected by [src/utils/xhr-loader.js][].
- if auto level switch is enabled and loaded frag level is greater than 0, this error is not fatal: in that case [src/controller/level-controller.js][] will trigger an emergency switch down to level 0.
- if frag level is 0 or auto level switch is disabled, this error is marked as fatal and a call to ```hls.startLoad()``` could help recover it.
- ```FRAG_LOOP_LOADING_ERROR``` is raised by [src/controller/mse-media-controller.js][] upon detection of same fragment being requested in loop. this could happen with badly formatted fragments.
- ```FRAG_LOOP_LOADING_ERROR``` is raised by [src/controller/stream-controller.js][] upon detection of same fragment being requested in loop. this could happen with badly formatted fragments.
- if auto level switch is enabled and loaded frag level is greater than 0, this error is not fatal: in that case [src/controller/level-controller.js][] will trigger an emergency switch down to level 0.
- if frag level is 0 or auto level switch is disabled, this error is marked as fatal and a call to ```hls.startLoad()``` could help recover it.
- ```FRAG_LOAD_TIMEOUT``` is raised by [src/loader/fragment-loader.js][] upon xhr timeout detected by [src/utils/xhr-loader.js][].
@ -165,5 +176,7 @@ design idea is pretty simple :
- if frag level is 0 or auto level switch is disabled, this error is marked as fatal and a call to ```hls.startLoad()``` could help recover it.
- ```FRAG_PARSING_ERROR``` is raised by [src/demux/tsdemuxer.js][] upon TS parsing error. this error is not fatal.
- ```FRAG_DECRYPT_ERROR``` is raised by [src/demux/demuxer.js][] upon fragment decrypting error. this error is fatal.
- ```BUFFER_APPEND_ERROR``` is raised by [src/controller/mse-media-controller.js][] when an exception is raised when calling sourceBuffer.appendBuffer(). this error is non fatal and become fatal after config.appendErrorMaxRetry retries. when fatal, a call to ```hls.recoverMediaError()``` could help recover it.
- ```BUFFER_APPENDING_ERROR``` is raised by [src/controller/mse-media-controller.js][] after SourceBuffer appending error. this error is fatal and a call to ```hls.recoverMediaError()``` could help recover it.
- ```BUFFER_APPEND_ERROR``` is raised by [src/controller/buffer-controller.js][] when an exception is raised when calling sourceBuffer.appendBuffer(). this error is non fatal and become fatal after config.appendErrorMaxRetry retries. when fatal, a call to ```hls.recoverMediaError()``` could help recover it.
- ```BUFFER_APPENDING_ERROR``` is raised by [src/controller/buffer-controller.js][] after SourceBuffer appending error. this error is fatal and a call to ```hls.recoverMediaError()``` could help recover it.
- ```BUFFER_STALLED_ERROR``` is raised by [src/controller/stream-controller.js][] if playback is stalling because of buffer underrun
- ```BUFFER_FULL_ERROR``` is raised by [src/controller/buffer-controller.js][] if sourcebuffer is full

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

@ -6,7 +6,8 @@
"folder_exclude_patterns": [
".git",
"node_modules",
"dist"
"dist",
"lib"
],
"file_exclude_patterns": [
".gitignore",

View file

@ -1,7 +1,7 @@
{
"name": "hls.js",
"version": "0.4.10",
"license" : "Apache-2.0",
"version": "0.5.7",
"license": "Apache-2.0",
"description": "Media Source Extension - HLS library, by/for Dailymotion",
"homepage": "https://github.com/dailymotion/hls.js",
"authors": "Guillaume du Pontavice <guillaume.dupontavice@dailymotion.com>",
@ -12,45 +12,49 @@
"bugs": {
"url": "https://github.com/dailymotion/hls.js/issues"
},
"main": "src/hls.js",
"main": "./lib/index.js",
"private": false,
"scripts": {
"clean": "find dist -mindepth 1 -delete",
"prebuild": "npm run clean & npm run test",
"build": "browserify -s Hls src/hls.js --debug | exorcist dist/hls.js.map -b . > dist/hls.js",
"build": "npm run babel && browserify -t [babelify] -s Hls src/index.js --debug | exorcist dist/hls.js.map -b . > dist/hls.js",
"postbuild": "npm run minify",
"prerelease": "npm run prebuild && npm run build && npm run postbuild && git add dist/* && git commit -m 'update dist'",
"patch": "npm run prerelease && mversion p",
"minor": "npm run prerelease && mversion mi",
"major": "npm run prerelease && mversion ma",
"minify": "uglifyjs dist/hls.js -c sequences=true,dead_code=true,conditionals=true,booleans=true,unused=true,if_return=true,join_vars=true,drop_console=true -m sort --screw-ie8 > dist/hls.min.js",
"watch": "watchify --debug -s Hls src/hls.js -o dist/hls.js",
"watch": "watchify --debug -s Hls src/index.js -t [babelify] -o dist/hls.js",
"pretest": "npm run lint",
"test": "mocha --compilers js:babel/register --recursive tests/unit",
"test": "mocha --compilers js:babel-register --recursive tests/unit",
"lint": "jshint src/",
"serve": "http-server -p 8000 .",
"open": "opener http://localhost:8000/demo/",
"live-reload": "live-reload --port 8001 dist/",
"dev": "npm run open -s & parallelshell 'npm run live-reload -s' 'npm run serve -s' 'npm run watch -s'"
},
"browserify": {
"transform": [
"babelify"
]
"dev": "npm run build && npm run open -s & parallelshell 'npm run live-reload -s' 'npm run serve -s' 'npm run watch -s'",
"babel": "babel src --out-dir lib"
},
"dependencies": {
"babelify": "^6.1.2",
"webworkify": "^1.0.2"
},
"devDependencies": {
"babel-preset-es2015": "^6.3.13",
"babel-register": "^6.3.13",
"babelify": "^7.2.0",
"arraybuffer-equal": "^1.0.4",
"babel": "^5.8.34",
"browserify": "^8.1.1",
"babel": "^6.3.26",
"babel-cli": "^6.3.17",
"browserify": "^13.0.0",
"deep-strict-equal": "^0.1.0",
"exorcist": "^0.4.0",
"http-server": "^0.7.4",
"http-server": "^0.9.0",
"jshint": "^2.5.11",
"live-reload": "^0.2.0",
"live-reload": "^1.1.0",
"mocha": "^2.1.0",
"mversion": "^1.10.1",
"opener": "^1.4.0",
"parallelshell": "^1.0.3",
"parallelshell": "^2.0.0",
"uglify-js": "^2.4.23",
"watchify": "^2.2.1"
"watchify": "^3.7.0"
}
}

View file

@ -20,7 +20,10 @@ class AbrController extends EventHandler {
onFragLoadProgress(data) {
var stats = data.stats;
if (stats.aborted === undefined) {
// only update stats if first frag loading
// if same frag is loaded multiple times, it might be in browser cache, and loaded quickly
// and leading to wrong bw estimation
if (stats.aborted === undefined && data.frag.loadCounter === 1) {
this.lastfetchduration = (performance.now() - stats.trequest) / 1000;
this.lastfetchlevel = data.frag.level;
this.lastbw = (stats.loaded * 8) / this.lastfetchduration;

View file

@ -0,0 +1,346 @@
/*
* Buffer Controller
*/
import Event from '../events';
import EventHandler from '../event-handler';
import {logger} from '../utils/logger';
import {ErrorTypes, ErrorDetails} from '../errors';
class BufferController extends EventHandler {
constructor(hls) {
super(hls,
Event.MEDIA_ATTACHING,
Event.MEDIA_DETACHING,
Event.BUFFER_RESET,
Event.BUFFER_APPENDING,
Event.BUFFER_CODECS,
Event.BUFFER_EOS,
Event.BUFFER_FLUSHING);
// Source Buffer listeners
this.onsbue = this.onSBUpdateEnd.bind(this);
this.onsbe = this.onSBUpdateError.bind(this);
}
destroy() {
EventHandler.prototype.destroy.call(this);
}
onMediaAttaching(data) {
var media = this.media = data.media;
// setup the media source
var ms = this.mediaSource = new MediaSource();
//Media Source listeners
this.onmso = this.onMediaSourceOpen.bind(this);
this.onmse = this.onMediaSourceEnded.bind(this);
this.onmsc = this.onMediaSourceClose.bind(this);
ms.addEventListener('sourceopen', this.onmso);
ms.addEventListener('sourceended', this.onmse);
ms.addEventListener('sourceclose', this.onmsc);
// link video and media Source
media.src = URL.createObjectURL(ms);
}
onMediaDetaching() {
var ms = this.mediaSource;
if (ms) {
if (ms.readyState === 'open') {
try {
// endOfStream could trigger exception if any sourcebuffer is in updating state
// we don't really care about checking sourcebuffer state here,
// as we are anyway detaching the MediaSource
// let's just avoid this exception to propagate
ms.endOfStream();
} catch(err) {
logger.warn(`onMediaDetaching:${err.message} while calling endOfStream`);
}
}
ms.removeEventListener('sourceopen', this.onmso);
ms.removeEventListener('sourceended', this.onmse);
ms.removeEventListener('sourceclose', this.onmsc);
// unlink MediaSource from video tag
this.media.src = '';
this.mediaSource = null;
this.media = null;
this.pendingTracks = null;
}
this.onmso = this.onmse = this.onmsc = null;
this.hls.trigger(Event.MEDIA_DETACHED);
}
onMediaSourceOpen() {
logger.log('media source opened');
this.hls.trigger(Event.MEDIA_ATTACHED, { media : this.media });
// once received, don't listen anymore to sourceopen event
this.mediaSource.removeEventListener('sourceopen', this.onmso);
// if any buffer codecs pending, treat it here.
var pendingTracks = this.pendingTracks;
if (pendingTracks) {
this.onBufferCodecs(pendingTracks);
this.pendingTracks = null;
this.doAppending();
}
}
onMediaSourceClose() {
logger.log('media source closed');
}
onMediaSourceEnded() {
logger.log('media source ended');
}
onSBUpdateEnd() {
if (this._needsFlush) {
this.doFlush();
}
if (this._needsEos) {
this.onBufferEos();
}
this.hls.trigger(Event.BUFFER_APPENDED);
this.doAppending();
}
onSBUpdateError(event) {
logger.error(`sourceBuffer error:${event}`);
// according to http://www.w3.org/TR/media-source/#sourcebuffer-append-error
// this error might not always be fatal (it is fatal if decode error is set, in that case
// it will be followed by a mediaElement error ...)
this.hls.trigger(Event.ERROR, {type: ErrorTypes.MEDIA_ERROR, details: ErrorDetails.BUFFER_APPENDING_ERROR, fatal: false});
// we don't need to do more than that, as accordin to the spec, updateend will be fired just after
}
onBufferReset() {
var sourceBuffer = this.sourceBuffer;
if (sourceBuffer) {
for(var type in sourceBuffer) {
var sb = sourceBuffer[type];
try {
this.mediaSource.removeSourceBuffer(sb);
sb.removeEventListener('updateend', this.onsbue);
sb.removeEventListener('error', this.onsbe);
} catch(err) {
}
}
this.sourceBuffer = null;
}
this.flushRange = [];
this.appended = 0;
}
onBufferCodecs(tracks) {
var sb,trackName,track, codec, mimeType;
if (!this.media) {
this.pendingTracks = tracks;
return;
}
if (!this.sourceBuffer) {
var sourceBuffer = {}, mediaSource = this.mediaSource;
for (trackName in tracks) {
track = tracks[trackName];
// use levelCodec as first priority
codec = track.levelCodec || track.codec;
mimeType = `${track.container};codecs=${codec}`;
logger.log(`creating sourceBuffer with mimeType:${mimeType}`);
sb = sourceBuffer[trackName] = mediaSource.addSourceBuffer(mimeType);
sb.addEventListener('updateend', this.onsbue);
sb.addEventListener('error', this.onsbe);
}
this.sourceBuffer = sourceBuffer;
}
}
onBufferAppending(data) {
if (!this.segments) {
this.segments = [ data ];
} else {
this.segments.push(data);
}
this.doAppending();
}
onBufferAppendFail(data) {
logger.error(`sourceBuffer error:${data.event}`);
// according to http://www.w3.org/TR/media-source/#sourcebuffer-append-error
// this error might not always be fatal (it is fatal if decode error is set, in that case
// it will be followed by a mediaElement error ...)
this.hls.trigger(Event.ERROR, {type: ErrorTypes.MEDIA_ERROR, details: ErrorDetails.BUFFER_APPENDING_ERROR, fatal: false, frag: this.fragCurrent});
}
onBufferEos() {
var sb = this.sourceBuffer, mediaSource = this.mediaSource;
if (!mediaSource || mediaSource.readyState !== 'open') {
return;
}
if (!((sb.audio && sb.audio.updating) || (sb.video && sb.video.updating))) {
logger.log('all media data available, signal endOfStream() to MediaSource and stop loading fragment');
//Notify the media element that it now has all of the media data
mediaSource.endOfStream();
this._needsEos = false;
} else {
this._needsEos = true;
}
}
onBufferFlushing(data) {
this.flushRange.push({start: data.startOffset, end: data.endOffset});
// attempt flush immediatly
this.flushBufferCounter = 0;
this.doFlush();
}
doFlush() {
// loop through all buffer ranges to flush
while(this.flushRange.length) {
var range = this.flushRange[0];
// flushBuffer will abort any buffer append in progress and flush Audio/Video Buffer
if (this.flushBuffer(range.start, range.end)) {
// range flushed, remove from flush array
this.flushRange.shift();
this.flushBufferCounter = 0;
} else {
this._needsFlush = true;
// avoid looping, wait for SB update end to retrigger a flush
return;
}
}
if (this.flushRange.length === 0) {
// everything flushed
this._needsFlush = false;
// let's recompute this.appended, which is used to avoid flush looping
var appended = 0;
var sourceBuffer = this.sourceBuffer;
if (sourceBuffer) {
for (var type in sourceBuffer) {
appended += sourceBuffer[type].buffered.length;
}
}
this.appended = appended;
this.hls.trigger(Event.BUFFER_FLUSHED);
}
}
doAppending() {
var hls = this.hls, sourceBuffer = this.sourceBuffer, segments = this.segments;
if (sourceBuffer) {
if (this.media.error) {
segments = [];
logger.error('trying to append although a media error occured, flush segment and abort');
return;
}
for (var type in sourceBuffer) {
if (sourceBuffer[type].updating) {
//logger.log('sb update in progress');
return;
}
}
if (segments.length) {
var segment = segments.shift();
try {
//logger.log(`appending ${segment.type} SB, size:${segment.data.length});
sourceBuffer[segment.type].appendBuffer(segment.data);
this.appendError = 0;
this.appended++;
} catch(err) {
// in case any error occured while appending, put back segment in segments table
logger.error(`error while trying to append buffer:${err.message}`);
segments.unshift(segment);
var event = {type: ErrorTypes.MEDIA_ERROR};
if(err.code !== 22) {
if (this.appendError) {
this.appendError++;
} else {
this.appendError = 1;
}
event.details = ErrorDetails.BUFFER_APPEND_ERROR;
event.frag = this.fragCurrent;
/* with UHD content, we could get loop of quota exceeded error until
browser is able to evict some data from sourcebuffer. retrying help recovering this
*/
if (this.appendError > hls.config.appendErrorMaxRetry) {
logger.log(`fail ${hls.config.appendErrorMaxRetry} times to append segment in sourceBuffer`);
segments = [];
event.fatal = true;
hls.trigger(Event.ERROR, event);
return;
} else {
event.fatal = false;
hls.trigger(Event.ERROR, event);
}
} else {
// QuotaExceededError: http://www.w3.org/TR/html5/infrastructure.html#quotaexceedederror
// let's stop appending any segments, and report BUFFER_FULL error
segments = [];
event.details = ErrorDetails.BUFFER_FULL;
hls.trigger(Event.ERROR,event);
}
}
}
}
}
/*
flush specified buffered range,
return true once range has been flushed.
as sourceBuffer.remove() is asynchronous, flushBuffer will be retriggered on sourceBuffer update end
*/
flushBuffer(startOffset, endOffset) {
var sb, i, bufStart, bufEnd, flushStart, flushEnd;
//logger.log('flushBuffer,pos/start/end: ' + this.media.currentTime + '/' + startOffset + '/' + endOffset);
// safeguard to avoid infinite looping : don't try to flush more than the nb of appended segments
if (this.flushBufferCounter < this.appended && this.sourceBuffer) {
for (var type in this.sourceBuffer) {
sb = this.sourceBuffer[type];
if (!sb.updating) {
for (i = 0; i < sb.buffered.length; i++) {
bufStart = sb.buffered.start(i);
bufEnd = sb.buffered.end(i);
// workaround firefox not able to properly flush multiple buffered range.
if (navigator.userAgent.toLowerCase().indexOf('firefox') !== -1 && endOffset === Number.POSITIVE_INFINITY) {
flushStart = startOffset;
flushEnd = endOffset;
} else {
flushStart = Math.max(bufStart, startOffset);
flushEnd = Math.min(bufEnd, endOffset);
}
/* sometimes sourcebuffer.remove() does not flush
the exact expected time range.
to avoid rounding issues/infinite loop,
only flush buffer range of length greater than 500ms.
*/
if (Math.min(flushEnd,bufEnd) - flushStart > 0.5 ) {
this.flushBufferCounter++;
logger.log(`flush ${type} [${flushStart},${flushEnd}], of [${bufStart},${bufEnd}], pos:${this.media.currentTime}`);
sb.remove(flushStart, flushEnd);
return false;
}
}
} else {
//logger.log('abort ' + type + ' append in progress');
// this will abort any appending in progress
//sb.abort();
logger.warn('cannot flush, sb updating in progress');
return false;
}
}
} else {
logger.warn('abort flushing too many retries');
}
logger.log('buffer flushed');
// everything flushed !
return true;
}
}
export default BufferController;

View file

@ -11,7 +11,7 @@ import ID3 from '../demux/id3';
this.observer = observer;
this.remuxerClass = remuxerClass;
this.remuxer = new this.remuxerClass(observer);
this._aacTrack = {type: 'audio', id :-1, sequenceNumber: 0, samples : [], len : 0};
this._aacTrack = {container : 'audio/adts', type: 'audio', id :-1, sequenceNumber: 0, samples : [], len : 0};
}
static probe(data) {
@ -83,7 +83,7 @@ import ID3 from '../demux/id3';
break;
}
}
this.remuxer.remux(this._aacTrack,{samples : []}, {samples : [ { pts: pts, dts : pts, unit : id3.payload} ]}, timeOffset);
this.remuxer.remux(this._aacTrack,{samples : []}, {samples : [ { pts: pts, dts : pts, unit : id3.payload} ]}, { samples: [] }, timeOffset);
}
destroy() {

View file

@ -6,12 +6,14 @@ import Event from '../events';
import {ErrorTypes, ErrorDetails} from '../errors';
import AACDemuxer from '../demux/aacdemuxer';
import TSDemuxer from '../demux/tsdemuxer';
import MP4Remuxer from '../remux/mp4-remuxer';
import PassThroughRemuxer from '../remux/passthrough-remuxer';
class DemuxerInline {
constructor(hls,remuxer) {
constructor(hls,typeSupported) {
this.hls = hls;
this.remuxer = remuxer;
this.typeSupported = typeSupported;
}
destroy() {
@ -24,15 +26,21 @@ class DemuxerInline {
push(data, audioCodec, videoCodec, timeOffset, cc, level, sn, duration) {
var demuxer = this.demuxer;
if (!demuxer) {
var hls = this.hls;
// probe for content type
if (TSDemuxer.probe(data)) {
demuxer = this.demuxer = new TSDemuxer(this.hls,this.remuxer);
} else if(AACDemuxer.probe(data)) {
demuxer = this.demuxer = new AACDemuxer(this.hls,this.remuxer);
if (this.typeSupported.mp2t === true) {
demuxer = new TSDemuxer(hls,PassThroughRemuxer);
} else {
this.hls.trigger(Event.ERROR, {type : ErrorTypes.MEDIA_ERROR, details: ErrorDetails.FRAG_PARSING_ERROR, fatal: true, reason: 'no demux matching with content found'});
demuxer = new TSDemuxer(hls,MP4Remuxer);
}
} else if(AACDemuxer.probe(data)) {
demuxer = new AACDemuxer(hls,MP4Remuxer);
} else {
hls.trigger(Event.ERROR, {type : ErrorTypes.MEDIA_ERROR, details: ErrorDetails.FRAG_PARSING_ERROR, fatal: true, reason: 'no demux matching with content found'});
return;
}
this.demuxer = demuxer;
}
demuxer.push(data,audioCodec,videoCodec,timeOffset,cc,level,sn,duration);
}

View file

@ -6,7 +6,6 @@
import DemuxerInline from '../demux/demuxer-inline';
import Event from '../events';
import EventEmitter from 'events';
import MP4Remuxer from '../remux/mp4-remuxer';
var DemuxerWorker = function (self) {
// observer setup
@ -19,13 +18,13 @@ var DemuxerWorker = function (self) {
observer.removeListener(event, ...data);
};
self.addEventListener('message', function (ev) {
//console.log('demuxer cmd:' + ev.data.cmd);
switch (ev.data.cmd) {
var data = ev.data;
//console.log('demuxer cmd:' + data.cmd);
switch (data.cmd) {
case 'init':
self.demuxer = new DemuxerInline(observer,MP4Remuxer);
self.demuxer = new DemuxerInline(observer, data.typeSupported);
break;
case 'demux':
var data = ev.data;
self.demuxer.push(new Uint8Array(data.data), data.audioCodec, data.videoCodec, data.timeOffset, data.cc, data.level, data.sn, data.duration);
break;
default:
@ -33,31 +32,15 @@ var DemuxerWorker = function (self) {
}
});
// listen to events triggered by TS Demuxer
// listen to events triggered by Demuxer
observer.on(Event.FRAG_PARSING_INIT_SEGMENT, function(ev, data) {
var objData = {event: ev};
var objTransferable = [];
if (data.audioCodec) {
objData.audioCodec = data.audioCodec;
objData.audioMoov = data.audioMoov.buffer;
objData.audioChannelCount = data.audioChannelCount;
objTransferable.push(objData.audioMoov);
}
if (data.videoCodec) {
objData.videoCodec = data.videoCodec;
objData.videoMoov = data.videoMoov.buffer;
objData.videoWidth = data.videoWidth;
objData.videoHeight = data.videoHeight;
objTransferable.push(objData.videoMoov);
}
// pass moov as transferable object (no copy)
self.postMessage(objData,objTransferable);
self.postMessage({event: ev, tracks : data.tracks, unique : data.unique });
});
observer.on(Event.FRAG_PARSING_DATA, function(ev, data) {
var objData = {event: ev, type: data.type, startPTS: data.startPTS, endPTS: data.endPTS, startDTS: data.startDTS, endDTS: data.endDTS, moof: data.moof.buffer, mdat: data.mdat.buffer, nb: data.nb};
// pass moof/mdat data as transferable object (no copy)
self.postMessage(objData, [objData.moof, objData.mdat]);
var objData = {event: ev, type: data.type, startPTS: data.startPTS, endPTS: data.endPTS, startDTS: data.startDTS, endDTS: data.endDTS, data1: data.data1.buffer, data2: data.data2.buffer, nb: data.nb};
// pass data1/data2 as transferable object (no copy)
self.postMessage(objData, [objData.data1, objData.data2]);
});
observer.on(Event.FRAG_PARSED, function(event) {

View file

@ -2,13 +2,16 @@ import Event from '../events';
import DemuxerInline from '../demux/demuxer-inline';
import DemuxerWorker from '../demux/demuxer-worker';
import {logger} from '../utils/logger';
import MP4Remuxer from '../remux/mp4-remuxer';
import Decrypter from '../crypt/decrypter';
class Demuxer {
constructor(hls) {
this.hls = hls;
var typeSupported = {
mp4 : MediaSource.isTypeSupported('video/mp4'),
mp2t : hls.config.enableMP2TPassThrough && MediaSource.isTypeSupported('video/mp2t')
};
if (hls.config.enableWorker && (typeof(Worker) !== 'undefined')) {
logger.log('demuxing in webworker');
try {
@ -16,13 +19,13 @@ class Demuxer {
this.w = work(DemuxerWorker);
this.onwmsg = this.onWorkerMessage.bind(this);
this.w.addEventListener('message', this.onwmsg);
this.w.postMessage({cmd: 'init'});
this.w.postMessage({cmd: 'init', typeSupported : typeSupported});
} catch(err) {
logger.error('error while initializing DemuxerWorker, fallback on DemuxerInline');
this.demuxer = new DemuxerInline(hls,MP4Remuxer);
this.demuxer = new DemuxerInline(hls,typeSupported);
}
} else {
this.demuxer = new DemuxerInline(hls,MP4Remuxer);
this.demuxer = new DemuxerInline(hls,typeSupported);
}
this.demuxInitialized = true;
}
@ -67,47 +70,39 @@ class Demuxer {
}
onWorkerMessage(ev) {
//console.log('onWorkerMessage:' + ev.data.event);
switch(ev.data.event) {
var data = ev.data;
//console.log('onWorkerMessage:' + data.event);
switch(data.event) {
case Event.FRAG_PARSING_INIT_SEGMENT:
var obj = {};
if (ev.data.audioMoov) {
obj.audioMoov = new Uint8Array(ev.data.audioMoov);
obj.audioCodec = ev.data.audioCodec;
obj.audioChannelCount = ev.data.audioChannelCount;
}
if (ev.data.videoMoov) {
obj.videoMoov = new Uint8Array(ev.data.videoMoov);
obj.videoCodec = ev.data.videoCodec;
obj.videoWidth = ev.data.videoWidth;
obj.videoHeight = ev.data.videoHeight;
}
obj.tracks = data.tracks;
obj.unique = data.unique;
this.hls.trigger(Event.FRAG_PARSING_INIT_SEGMENT, obj);
break;
case Event.FRAG_PARSING_DATA:
this.hls.trigger(Event.FRAG_PARSING_DATA,{
moof: new Uint8Array(ev.data.moof),
mdat: new Uint8Array(ev.data.mdat),
startPTS: ev.data.startPTS,
endPTS: ev.data.endPTS,
startDTS: ev.data.startDTS,
endDTS: ev.data.endDTS,
type: ev.data.type,
nb: ev.data.nb
data1: new Uint8Array(data.data1),
data2: new Uint8Array(data.data2),
startPTS: data.startPTS,
endPTS: data.endPTS,
startDTS: data.startDTS,
endDTS: data.endDTS,
type: data.type,
nb: data.nb
});
break;
case Event.FRAG_PARSING_METADATA:
this.hls.trigger(Event.FRAG_PARSING_METADATA, {
samples: ev.data.samples
samples: data.samples
});
break;
case Event.FRAG_PARSING_USERDATA:
this.hls.trigger(Event.FRAG_PARSING_USERDATA, {
samples: ev.data.samples
samples: data.samples
});
break;
default:
this.hls.trigger(ev.data.event, ev.data.data);
this.hls.trigger(data.event, data.data);
break;
}
}

View file

@ -249,7 +249,7 @@ class ExpGolomb {
let sarRatio;
const aspectRatioIdc = this.readUByte();
switch (aspectRatioIdc) {
//case 1: sarRatio = [1,1]; break;
case 1: sarRatio = [1,1]; break;
case 2: sarRatio = [12,11]; break;
case 3: sarRatio = [10,11]; break;
case 4: sarRatio = [16,11]; break;
@ -276,7 +276,7 @@ class ExpGolomb {
}
}
return {
width: (((picWidthInMbsMinus1 + 1) * 16) - frameCropLeftOffset * 2 - frameCropRightOffset * 2) * sarScale,
width: Math.ceil((((picWidthInMbsMinus1 + 1) * 16) - frameCropLeftOffset * 2 - frameCropRightOffset * 2) * sarScale),
height: ((2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16) - ((frameMbsOnlyFlag? 2 : 4) * (frameCropTopOffset + frameCropBottomOffset))
};
}

View file

@ -23,7 +23,6 @@
this.remuxerClass = remuxerClass;
this.lastCC = 0;
this.remuxer = new this.remuxerClass(observer);
this._userData = [];
}
static probe(data) {
@ -40,8 +39,8 @@
this._pmtId = -1;
this.lastAacPTS = null;
this.aacOverFlow = null;
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._avcTrack = {container : 'video/mp2t', type: 'video', id :-1, sequenceNumber: 0, samples : [], len : 0, nbNalu : 0};
this._aacTrack = {container : 'video/mp2t', type: 'audio', 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();
@ -55,7 +54,9 @@
// feed incoming data to the front of the parsing pipeline
push(data, audioCodec, videoCodec, timeOffset, cc, level, sn, duration) {
var avcData, aacData, id3Data,
start, len = data.length, stt, pid, atf, offset;
start, len = data.length, stt, pid, atf, offset,
codecsOnly = this.remuxer.passthrough;
this.audioCodec = audioCodec;
this.videoCodec = videoCodec;
this.timeOffset = timeOffset;
@ -108,6 +109,15 @@
if (stt) {
if (avcData) {
this._parseAVCPES(this._parsePES(avcData));
if (codecsOnly) {
// if we have video codec info AND
// if audio PID is undefined OR if we have audio codec info,
// we have all codec info !
if (this._avcTrack.codec && (aacId === -1 || this._aacTrack.codec)) {
this.remux(data);
return;
}
}
}
avcData = {data: [], size: 0};
}
@ -119,6 +129,15 @@
if (stt) {
if (aacData) {
this._parseAACPES(this._parsePES(aacData));
if (codecsOnly) {
// here we now that we have audio codec info
// if video PID is undefined OR if we have video codec info,
// we have all codec infos !
if (this._aacTrack.codec && (avcId === -1 || this._avcTrack.codec)) {
this.remux(data);
return;
}
}
}
aacData = {data: [], size: 0};
}
@ -166,11 +185,11 @@
if (id3Data) {
this._parseID3PES(this._parsePES(id3Data));
}
this.remux();
this.remux(null);
}
remux() {
this.remuxer.remux(this._aacTrack, this._avcTrack, this._id3Track, this._txtTrack, this.timeOffset, this.contiguous);
remux(data) {
this.remuxer.remux(this._aacTrack, this._avcTrack, this._id3Track, this._txtTrack, this.timeOffset, this.contiguous, data);
}
destroy() {
@ -223,9 +242,9 @@
}
_parsePES(stream) {
var i = 0, frag, pesFlags, pesPrefix, pesLen, pesHdrLen, pesData, pesPts, pesDts, payloadStartOffset;
var i = 0, frag, pesFlags, pesPrefix, pesLen, pesHdrLen, pesData, pesPts, pesDts, payloadStartOffset, data = stream.data;
//retrieve PTS/DTS from first fragment
frag = stream.data[0];
frag = data[0];
pesPrefix = (frag[0] << 16) + (frag[1] << 8) + frag[2];
if (pesPrefix === 1) {
pesLen = (frag[4] << 8) + frag[5];
@ -261,16 +280,27 @@
}
pesHdrLen = frag[8];
payloadStartOffset = pesHdrLen + 9;
// trim PES header
stream.data[0] = stream.data[0].subarray(payloadStartOffset);
stream.size -= payloadStartOffset;
//reassemble PES packet
pesData = new Uint8Array(stream.size);
// reassemble the packet
while (stream.data.length) {
frag = stream.data.shift();
while (data.length) {
frag = data.shift();
var len = frag.byteLength;
if (payloadStartOffset) {
if (payloadStartOffset > len) {
// trim full frag if PES header bigger than frag
payloadStartOffset-=len;
continue;
} else {
// trim partial frag if PES header smaller than frag
frag = frag.subarray(payloadStartOffset);
len-=payloadStartOffset;
payloadStartOffset = 0;
}
}
pesData.set(frag, i);
i += frag.byteLength;
i+=len;
}
return {data: pesData, pts: pesPts, dts: pesDts, len: pesLen};
} else {

View file

@ -39,5 +39,7 @@ export const ErrorDetails = {
// Identifier for a buffer appending error event - data: appending error description
BUFFER_APPENDING_ERROR: 'bufferAppendingError',
// Identifier for a buffer stalled error event
BUFFER_STALLED_ERROR: 'bufferStalledError'
BUFFER_STALLED_ERROR: 'bufferStalledError',
// Identifier for a buffer full error event
BUFFER_FULL_ERROR: 'bufferFullError'
};

View file

@ -7,6 +7,20 @@ module.exports = {
MEDIA_DETACHING: 'hlsMediaDetaching',
// fired when MediaSource has been detached from media element - data: { }
MEDIA_DETACHED: 'hlsMediaDetached',
// fired when we buffer is going to be resetted
BUFFER_RESET: 'hlsBufferReset',
// fired when we know about the codecs that we need buffers for to push into - data: {tracks : { container, codec, levelCodec, initSegment, metadata }}
BUFFER_CODECS: 'hlsBufferCodecs',
// fired when we append a segment to the buffer - data: { segment: segment object }
BUFFER_APPENDING: 'hlsBufferAppending',
// fired when we are done with appending a media segment to the buffer
BUFFER_APPENDED: 'hlsBufferAppended',
// fired when the stream is finished and we want to notify the media buffer that there will be no more data
BUFFER_EOS: 'hlsBufferEos',
// fired when the media buffer should be flushed - data {startOffset, endOffset}
BUFFER_FLUSHING: 'hlsBufferFlushing',
// fired when the media has been flushed
BUFFER_FLUSHED: 'hlsBufferFlushed',
// fired to signal that a manifest loading starts - data: { url : manifestURL}
MANIFEST_LOADING: 'hlsManifestLoading',
// fired after manifest has been loaded - data: { levels : [available quality levels] , url : manifestURL, stats : { trequest, tfirst, tload, mtime}}
@ -37,7 +51,7 @@ module.exports = {
FRAG_PARSING_USERDATA: 'hlsFragParsingUserdata',
// fired when parsing id3 is completed - data: { samples : [ id3 samples pes ] }
FRAG_PARSING_METADATA: 'hlsFragParsingMetadata',
// fired when moof/mdat have been extracted from fragment - data: { moof : moof MP4 box, mdat : mdat MP4 box}
// fired when data have been extracted from fragment - data: { data1 : moof MP4 box or TS fragments, data2 : mdat MP4 box or null}
FRAG_PARSING_DATA: 'hlsFragParsingData',
// fired when fragment parsing is completed - data: undefined
FRAG_PARSED: 'hlsFragParsed',

View file

@ -8,7 +8,8 @@ import {ErrorTypes, ErrorDetails} from './errors';
import PlaylistLoader from './loader/playlist-loader';
import FragmentLoader from './loader/fragment-loader';
import AbrController from './controller/abr-controller';
import MSEMediaController from './controller/mse-media-controller';
import BufferController from './controller/buffer-controller';
import StreamController from './controller/stream-controller';
import LevelController from './controller/level-controller';
import TimelineController from './controller/timeline-controller';
//import FPSController from './controller/fps-controller';
@ -59,6 +60,7 @@ class Hls {
fragLoadingMaxRetry: 6,
fragLoadingRetryDelay: 1000,
fragLoadingLoopThreshold: 3,
startFragPrefetch : false,
// fpsDroppedMonitoringPeriod: 5000,
// fpsDroppedMonitoringThreshold: 0.2,
appendErrorMaxRetry: 3,
@ -66,9 +68,11 @@ class Hls {
fLoader: undefined,
pLoader: undefined,
abrController : AbrController,
mediaController: MSEMediaController,
bufferController : BufferController,
streamController: StreamController,
timelineController: TimelineController,
enableCEA708Captions: true
enableCEA708Captions: true,
enableMP2TPassThrough : false
};
}
return Hls.defaultConfig;
@ -107,7 +111,8 @@ class Hls {
this.fragmentLoader = new FragmentLoader(this);
this.levelController = new LevelController(this);
this.abrController = new config.abrController(this);
this.mediaController = new config.mediaController(this);
this.bufferController = new config.bufferController(this);
this.streamController = new config.streamController(this);
this.timelineController = new config.timelineController(this);
this.keyLoader = new KeyLoader(this);
//this.fpsController = new FPSController(this);
@ -120,7 +125,8 @@ class Hls {
this.playlistLoader.destroy();
this.fragmentLoader.destroy();
this.levelController.destroy();
this.mediaController.destroy();
this.bufferController.destroy();
this.streamController.destroy();
this.timelineController.destroy();
this.keyLoader.destroy();
//this.fpsController.destroy();
@ -149,12 +155,12 @@ class Hls {
startLoad() {
logger.log('startLoad');
this.mediaController.startLoad();
this.streamController.startLoad();
}
swapAudioCodec() {
logger.log('swapAudioCodec');
this.mediaController.swapAudioCodec();
this.streamController.swapAudioCodec();
}
recoverMediaError() {
@ -171,26 +177,26 @@ class Hls {
/** Return current playback quality level **/
get currentLevel() {
return this.mediaController.currentLevel;
return this.streamController.currentLevel;
}
/* set quality level immediately (-1 for automatic level selection) */
set currentLevel(newLevel) {
logger.log(`set currentLevel:${newLevel}`);
this.loadLevel = newLevel;
this.mediaController.immediateLevelSwitch();
this.streamController.immediateLevelSwitch();
}
/** Return next playback quality level (quality level of next fragment) **/
get nextLevel() {
return this.mediaController.nextLevel;
return this.streamController.nextLevel;
}
/* set quality level for next fragment (-1 for automatic level selection) */
set nextLevel(newLevel) {
logger.log(`set nextLevel:${newLevel}`);
this.levelController.manualLevel = newLevel;
this.mediaController.nextLevelSwitch();
this.streamController.nextLevelSwitch();
}
/** Return the quality level of current/last loaded fragment **/

View file

@ -0,0 +1,4 @@
// This is mostly for support of the es6 module export
// syntax with the babel compiler, it looks like it doesnt support
// function exports like we are used to in node/commonjs
module.exports = require('./hls.js').default;

View file

@ -8,6 +8,10 @@ class DummyRemuxer {
this.observer = observer;
}
get passthrough() {
return false;
}
get timescale() {
return this.PES_TIMESCALE;
}

View file

@ -17,6 +17,10 @@ class MP4Remuxer {
this.MP4_TIMESCALE = this.PES_TIMESCALE / this.PES2MP4SCALEFACTOR;
}
get passthrough() {
return false;
}
get timescale() {
return this.MP4_TIMESCALE;
}
@ -61,62 +65,55 @@ class MP4Remuxer {
var observer = this.observer,
audioSamples = audioTrack.samples,
videoSamples = videoTrack.samples,
nbAudio = audioSamples.length,
nbVideo = videoSamples.length,
pesTimeScale = this.PES_TIMESCALE;
pesTimeScale = this.PES_TIMESCALE,
tracks = {},
data = { tracks : tracks, unique : false },
computePTSDTS = (this._initPTS === undefined),
initPTS, initDTS;
if(nbAudio === 0 && nbVideo === 0) {
if (computePTSDTS) {
initPTS = initDTS = Infinity;
}
if (audioTrack.config && audioSamples.length) {
tracks.audio = {
container : 'audio/mp4',
codec : audioTrack.codec,
initSegment : MP4.initSegment([audioTrack]),
metadata : {
channelCount : audioTrack.channelCount
}
};
if (computePTSDTS) {
// remember first PTS of this demuxing context. for audio, PTS + DTS ...
initPTS = initDTS = audioSamples[0].pts - pesTimeScale * timeOffset;
}
}
if (videoTrack.sps && videoTrack.pps && videoSamples.length) {
tracks.video = {
container : 'video/mp4',
codec : videoTrack.codec,
initSegment : MP4.initSegment([videoTrack]),
metadata : {
width : videoTrack.width,
height : videoTrack.height
}
};
if (computePTSDTS) {
initPTS = Math.min(initPTS,videoSamples[0].pts - pesTimeScale * timeOffset);
initDTS = Math.min(initDTS,videoSamples[0].dts - pesTimeScale * timeOffset);
}
}
if(!Object.keys(tracks)) {
observer.trigger(Event.ERROR, {type : ErrorTypes.MEDIA_ERROR, details: ErrorDetails.FRAG_PARSING_ERROR, fatal: false, reason: 'no audio/video samples found'});
} else if (nbVideo === 0) {
//audio only
if (audioTrack.config) {
observer.trigger(Event.FRAG_PARSING_INIT_SEGMENT, {
audioMoov: MP4.initSegment([audioTrack]),
audioCodec : audioTrack.codec,
audioChannelCount : audioTrack.channelCount
});
this.ISGenerated = true;
}
if (this._initPTS === undefined) {
// remember first PTS of this demuxing context
this._initPTS = audioSamples[0].pts - pesTimeScale * timeOffset;
this._initDTS = audioSamples[0].dts - pesTimeScale * timeOffset;
}
} else
if (nbAudio === 0) {
//video only
if (videoTrack.sps && videoTrack.pps) {
observer.trigger(Event.FRAG_PARSING_INIT_SEGMENT, {
videoMoov: MP4.initSegment([videoTrack]),
videoCodec: videoTrack.codec,
videoWidth: videoTrack.width,
videoHeight: videoTrack.height
});
this.ISGenerated = true;
if (this._initPTS === undefined) {
// remember first PTS of this demuxing context
this._initPTS = videoSamples[0].pts - pesTimeScale * timeOffset;
this._initDTS = videoSamples[0].dts - pesTimeScale * timeOffset;
}
}
} else {
//audio and video
if (audioTrack.config && videoTrack.sps && videoTrack.pps) {
observer.trigger(Event.FRAG_PARSING_INIT_SEGMENT, {
audioMoov: MP4.initSegment([audioTrack]),
audioCodec: audioTrack.codec,
audioChannelCount: audioTrack.channelCount,
videoMoov: MP4.initSegment([videoTrack]),
videoCodec: videoTrack.codec,
videoWidth: videoTrack.width,
videoHeight: videoTrack.height
});
observer.trigger(Event.FRAG_PARSING_INIT_SEGMENT,data);
this.ISGenerated = true;
if (this._initPTS === undefined) {
// remember first PTS of this demuxing context
this._initPTS = Math.min(videoSamples[0].pts, audioSamples[0].pts) - pesTimeScale * timeOffset;
this._initDTS = Math.min(videoSamples[0].dts, audioSamples[0].dts) - pesTimeScale * timeOffset;
}
if (computePTSDTS) {
this._initPTS = initPTS;
this._initDTS = initDTS;
}
}
}
@ -238,8 +235,8 @@ class MP4Remuxer {
moof = MP4.moof(track.sequenceNumber++, firstDTS / pes2mp4ScaleFactor, track);
track.samples = [];
this.observer.trigger(Event.FRAG_PARSING_DATA, {
moof: moof,
mdat: mdat,
data1: moof,
data2: mdat,
startPTS: firstPTS / pesTimeScale,
endPTS: (ptsnorm + pes2mp4ScaleFactor * lastSampleDuration) / pesTimeScale,
startDTS: firstDTS / pesTimeScale,
@ -358,8 +355,8 @@ class MP4Remuxer {
moof = MP4.moof(track.sequenceNumber++, firstDTS / pes2mp4ScaleFactor, track);
track.samples = [];
this.observer.trigger(Event.FRAG_PARSING_DATA, {
moof: moof,
mdat: mdat,
data1: moof,
data2: mdat,
startPTS: firstPTS / pesTimeScale,
endPTS: this.nextAacPts / pesTimeScale,
startDTS: firstDTS / pesTimeScale,

View file

@ -0,0 +1,75 @@
/**
* passthrough remuxer
*/
import Event from '../events';
class PassThroughRemuxer {
constructor(observer) {
this.observer = observer;
this.ISGenerated = false;
}
get passthrough() {
return true;
}
get timescale() {
return 0;
}
destroy() {
}
insertDiscontinuity() {
}
switchLevel() {
this.ISGenerated = false;
}
remux(audioTrack,videoTrack,id3Track,textTrack,timeOffset,rawData) {
var observer = this.observer;
// generate Init Segment if needed
if (!this.ISGenerated) {
var tracks = {},
data = { tracks : tracks, unique : true },
track = videoTrack,
codec = track.codec;
if (codec) {
data.tracks.video = {
container : track.container,
codec : codec,
metadata : {
width : track.width,
height : track.height
}
};
}
track = audioTrack;
codec = track.codec;
if (codec) {
data.tracks.audio = {
container : track.container,
codec : codec,
metadata : {
channelCount : track.channelCount
}
};
}
this.ISGenerated = true;
observer.trigger(Event.FRAG_PARSING_INIT_SEGMENT,data);
}
observer.trigger(Event.FRAG_PARSING_DATA, {
data1: rawData,
startPTS: timeOffset,
startDTS: timeOffset,
type: 'audiovideo',
nb: 1
});
}
}
export default PassThroughRemuxer;

View file

@ -25,27 +25,28 @@ class CEA708Interpreter {
{
var VTTCue = window.VTTCue || window.TextTrackCue;
this.cue = new VTTCue(-1, -1, '');
this.cue.text = '';
this.cue.pauseOnExit = false;
var cue = this.cue = new VTTCue(-1, -1, '');
cue.text = '';
cue.pauseOnExit = false;
// make sure it doesn't show up before it's ready
this.startTime = Number.MAX_VALUE;
cue.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;
cue.endTime = Number.MAX_VALUE;
this.memory.push(this.cue);
this.memory.push(cue);
}
clear()
{
if (this._textTrack && this._textTrack.cues)
var textTrack = this._textTrack;
if (textTrack && textTrack.cues)
{
while (this._textTrack.cues.length > 0)
while (textTrack.cues.length > 0)
{
this._textTrack.removeCue(this._textTrack.cues[0]);
textTrack.removeCue(textTrack.cues[0]);
}
}
}
@ -59,15 +60,15 @@ class CEA708Interpreter {
var count = bytes[0] & 31;
var position = 2;
var byte, ccbyte1, ccbyte2, ccValid, ccType;
var tmpByte, ccbyte1, ccbyte2, ccValid, ccType;
for (var j=0; j<count; j++)
{
byte = bytes[position++];
tmpByte = bytes[position++];
ccbyte1 = 0x7F & bytes[position++];
ccbyte2 = 0x7F & bytes[position++];
ccValid = ((4 & byte) === 0 ? false : true);
ccType = (3 & byte);
ccValid = ((4 & tmpByte) === 0 ? false : true);
ccType = (3 & tmpByte);
if (ccbyte1 === 0 && ccbyte2 === 0)
{
@ -287,9 +288,9 @@ class CEA708Interpreter {
}
}
_fromCharCode(byte)
_fromCharCode(tmpByte)
{
switch (byte)
switch (tmpByte)
{
case 42:
return 'á';
@ -325,7 +326,7 @@ class CEA708Interpreter {
return '█';
default:
return String.fromCharCode(byte);
return String.fromCharCode(tmpByte);
}
}
@ -343,11 +344,11 @@ class CEA708Interpreter {
this._has708 = true;
}
for (var i=0; i<this.memory.length; i++)
for(let memoryItem of this.memory)
{
this.memory[i].startTime = timestamp;
this._textTrack.addCue(this.memory[i]);
this.display.push(this.memory[i]);
memoryItem.startTime = timestamp;
this._textTrack.addCue(memoryItem);
this.display.push(memoryItem);
}
this.memory = [];
@ -356,9 +357,9 @@ class CEA708Interpreter {
_clearActiveCues(timestamp)
{
for (var i=0; i<this.display.length; i++)
for (let displayItem of this.display)
{
this.display[i].endTime = timestamp;
displayItem.endTime = timestamp;
}
this.display = [];

View file

@ -30,14 +30,14 @@
"webcomponentsjs": "webcomponents/webcomponentsjs#^0.7.0"
},
"ignore": [],
"homepage": "https://github.com/PolymerElements/iron-a11y-keys-behavior",
"homepage": "https://github.com/polymerelements/iron-a11y-keys-behavior",
"_release": "1.1.1",
"_resolution": {
"type": "version",
"tag": "v1.1.1",
"commit": "12af7cb19b2c6b3887e37a5ea1501ffe676d1e8a"
},
"_source": "git://github.com/PolymerElements/iron-a11y-keys-behavior.git",
"_source": "git://github.com/polymerelements/iron-a11y-keys-behavior.git",
"_target": "^1.0.0",
"_originalSource": "PolymerElements/iron-a11y-keys-behavior"
"_originalSource": "polymerelements/iron-a11y-keys-behavior"
}

View file

@ -36,7 +36,7 @@
"tag": "v1.2.4",
"commit": "1ee4e2e11a9e5118320987d93fc2c03ae9a489f4"
},
"_source": "git://github.com/polymerelements/iron-selector.git",
"_source": "git://github.com/PolymerElements/iron-selector.git",
"_target": "^1.0.0",
"_originalSource": "polymerelements/iron-selector"
"_originalSource": "PolymerElements/iron-selector"
}

View file

@ -45,7 +45,7 @@
"tag": "v1.0.11",
"commit": "e3c1ab0c72905b58fb4d9adc2921ea73b5c085a5"
},
"_source": "git://github.com/polymerelements/paper-behaviors.git",
"_source": "git://github.com/PolymerElements/paper-behaviors.git",
"_target": "^1.0.0",
"_originalSource": "polymerelements/paper-behaviors"
"_originalSource": "PolymerElements/paper-behaviors"
}

View file

@ -32,14 +32,14 @@
"iron-test-helpers": "PolymerElements/iron-test-helpers#^1.0.0"
},
"ignore": [],
"homepage": "https://github.com/polymerelements/paper-ripple",
"homepage": "https://github.com/PolymerElements/paper-ripple",
"_release": "1.0.5",
"_resolution": {
"type": "version",
"tag": "v1.0.5",
"commit": "d72e7a9a8ab518b901ed18dde492df3b87a93be5"
},
"_source": "git://github.com/polymerelements/paper-ripple.git",
"_source": "git://github.com/PolymerElements/paper-ripple.git",
"_target": "^1.0.0",
"_originalSource": "polymerelements/paper-ripple"
"_originalSource": "PolymerElements/paper-ripple"
}

View file

@ -761,7 +761,7 @@ prevent = dy > dx;
prevent = dx > dy;
}
if (prevent) {
//ev.preventDefault();
ev.preventDefault();
} else {
Gestures.prevent('track');
}

View file

@ -73,15 +73,6 @@
</paper-item-body>
</paper-icon-item>
</a>
<a href="pin.html" class="clearLink">
<paper-icon-item>
<paper-fab mini icon="lock" class="green" item-icon></paper-fab>
<paper-item-body two-line>
<div>${ButtonEnterPinCode}</div>
<div secondary>${ButtonEnterPinCodeHelp}</div>
</paper-item-body>
</paper-icon-item>
</a>
<paper-icon-item class="btnLogout">
<paper-fab mini icon="lock" style="background-color: #444;" item-icon></paper-fab>
<paper-item-body two-line>

View file

@ -1,29 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Emby</title>
</head>
<body>
<div id="pinEntryPage" data-role="page" class="page libraryPage userPreferencesPage noSecondaryNavPage" data-contextname="${HeaderSettings}" data-require="scripts/pin,paper-input" data-backbutton="true">
<div data-role="content">
<form style="margin: auto;">
<h1>${HeaderEnterPinCode}</h1>
<p style="margin-top:2em;">${ButtonEnterPinCodeHelp}</p>
<paper-input type="number" id="txtPin" pattern="[0-9]*" required="required" min="0" max="999999" step="1" label="${LabelPinCode}"></paper-input>
<br />
<button type="submit" data-role="none" class="clearButton">
<paper-button raised class="accent block"><iron-icon icon="check"></iron-icon><span>${ButtonSignIn}</span></paper-button>
</button>
<paper-button raised class="cancel block btnCancel"><iron-icon icon="close"></iron-icon><span>${ButtonCancel}</span></paper-button>
<br />
<br />
</form>
</div>
</div>
</body>
</html>

View file

@ -1,63 +0,0 @@
define([], function () {
function onSubmit(e) {
var form = e.target;
Dashboard.showLoadingMsg();
ApiClient.ajax({
type: "POST",
url: ApiClient.getUrl('Auth/Pin/Validate'),
data: JSON.stringify({
Pin: form.querySelector('#txtPin').value
}),
contentType: "application/json",
dataType: 'json'
}).then(function (result) {
Dashboard.hideLoadingMsg();
Dashboard.alert({
message: Globalize.translate('PinCodeConfirmedMessage', result.AppName),
title: Globalize.translate('HeaderThankYou'),
callback: function () {
Dashboard.navigate('index.html');
}
});
}, function () {
Dashboard.hideLoadingMsg();
Dashboard.alert({
message: Globalize.translate('PinCodeInvalidMessage'),
title: Globalize.translate('PinCodeInvalid')
});
});
// Disable default form submission
e.preventDefault();
return false;
}
pageIdOn('pageinit', 'pinEntryPage', function () {
var page = this;
page.querySelector('form').addEventListener('submit', onSubmit);
page.querySelector('.btnCancel').addEventListener('click', function () {
Dashboard.navigate('mypreferencesmenu.html?userId=' + ApiClient.getCurrentUserId());
});
});
pageIdOn('pageshow', 'pinEntryPage', function () {
var page = this;
var txtPin = page.querySelector('#txtPin');
txtPin.focus();
txtPin.value = '';
});
});

View file

@ -1369,8 +1369,6 @@
"TitleForgotPassword": "Forgot Password",
"TitlePasswordReset": "Password Reset",
"LabelPasswordRecoveryPinCode": "Pin code:",
"HeaderEnterPinCode": "Enter Pin Code",
"ButtonEnterPinCode": "Enter pin code",
"HeaderPasswordReset": "Password Reset",
"HeaderParentalRatings": "Parental Ratings",
"HeaderVideoTypes": "Video Types",
@ -1543,7 +1541,6 @@
"OptionConvertRecordingsToStreamingFormat": "Automatically convert recordings to a streaming friendly format",
"OptionConvertRecordingsToStreamingFormatHelp": "Recordings will be converted on the fly to MP4 for easy playback on your devices.",
"FeatureRequiresEmbyPremiere": "This feature requires an active Emby Premiere subscription.",
"ButtonEnterPinCodeHelp": "Sign in to an Emby TV app by entering the on-screen pin code.",
"FileExtension": "File extension",
"OptionReplaceExistingImages": "Replace existing images"
}

View file

@ -969,8 +969,5 @@
"HeaderSelectCodecIntrosPath": "Select Codec Intros Path",
"ButtonLocalRefresh": "Local refresh",
"ButtonAddMissingData": "Add missing data only",
"ButtonFullRefresh": "Full refresh",
"PinCodeInvalid": "Pin Code Error",
"PinCodeInvalidMessage": "Invalid or expired pin code entered. Please try again.",
"PinCodeConfirmedMessage": "Pin code confirmed. {0} will automatically sign you in."
"ButtonFullRefresh": "Full refresh"
}