Skip to content

Commit

Permalink
Merge branch 'main' into fix/seekable-range-calculation
Browse files Browse the repository at this point in the history
  • Loading branch information
alex-barstow authored Nov 15, 2024
2 parents be7a3ff + 9f1c4ad commit 4870874
Show file tree
Hide file tree
Showing 11 changed files with 470 additions and 31 deletions.
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
<a name="3.15.0"></a>
# [3.15.0](https://github.com/videojs/http-streaming/compare/v3.14.2...v3.15.0) (2024-10-10)

### Features

* Add Airplay support when overriding native HLS in Safari/iOS ([#1543](https://github.com/videojs/http-streaming/issues/1543)) ([bfc17b4](https://github.com/videojs/http-streaming/commit/bfc17b4))
* Add support for ManagedMediaSource 'startstreaming' and 'endstream' event handling ([#1542](https://github.com/videojs/http-streaming/issues/1542)) ([ae1ae70](https://github.com/videojs/http-streaming/commit/ae1ae70))

### Chores

* update mpd-parser to v1.3.1 ([#1544](https://github.com/videojs/http-streaming/issues/1544)) ([a9dd790](https://github.com/videojs/http-streaming/commit/a9dd790))

<a name="3.14.2"></a>
## [3.14.2](https://github.com/videojs/http-streaming/compare/v3.14.1...v3.14.2) (2024-09-17)

Expand Down
29 changes: 19 additions & 10 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@videojs/http-streaming",
"version": "3.14.2",
"version": "3.15.0",
"description": "Play back HLS and DASH with Video.js, even where it's not natively supported",
"main": "dist/videojs-http-streaming.cjs.js",
"module": "dist/videojs-http-streaming.es.js",
Expand Down Expand Up @@ -62,8 +62,8 @@
"aes-decrypter": "^4.0.2",
"global": "^4.4.0",
"m3u8-parser": "^7.2.0",
"mpd-parser": "^1.3.0",
"mux.js": "7.0.3",
"mpd-parser": "^1.3.1",
"mux.js": "7.1.0",
"video.js": "^7 || ^8"
},
"peerDependencies": {
Expand Down
53 changes: 53 additions & 0 deletions src/media-segment-request.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ export const REQUEST_ERRORS = {
ABORTED: -102
};

const WEB_VTT_CODEC = 'wvtt';

/**
* Abort all requests
*
Expand Down Expand Up @@ -164,6 +166,43 @@ const handleKeyResponse = (segment, objects, finishProcessingFn, triggerSegmentE
return finishProcessingFn(null, segment);
};

/**
* Processes an mp4 init segment depending on the codec through the transmuxer.
*
* @param {Object} segment init segment to process
* @param {string} codec the codec of the text segments
*/
const initMp4Text = (segment, codec) => {
if (codec === WEB_VTT_CODEC) {
segment.transmuxer.postMessage({
action: 'initMp4WebVttParser',
data: segment.map.bytes
});
}
};

/**
* Parses an mp4 text segment with the transmuxer and calls the doneFn from
* the segment loader.
*
* @param {Object} segment the text segment to parse
* @param {string} codec the codec of the text segment
* @param {Function} doneFn the doneFn passed from the segment loader
*/
const parseMp4TextSegment = (segment, codec, doneFn) => {
if (codec === WEB_VTT_CODEC) {
workerCallback({
action: 'getMp4WebVttText',
data: segment.bytes,
transmuxer: segment.transmuxer,
callback: ({data, mp4VttCues}) => {
segment.bytes = data;
doneFn(null, segment, { mp4VttCues });
}
});
}
};

const parseInitSegment = (segment, callback) => {
const type = detectContainerForBytes(segment.map.bytes);

Expand Down Expand Up @@ -206,6 +245,10 @@ const parseInitSegment = (segment, callback) => {
segment.map.timescales[track.id] = track.timescale;
}

if (track.type === 'text') {
initMp4Text(segment, track.codec);
}

});

return callback(null);
Expand Down Expand Up @@ -468,6 +511,16 @@ const handleSegmentBytes = ({
if (isLikelyFmp4MediaSegment(bytesAsUint8Array)) {
segment.isFmp4 = true;
const {tracks} = segment.map;
const isMp4TextSegment = tracks.text && (!tracks.audio || !tracks.video);

if (isMp4TextSegment) {
dataFn(segment, {
data: bytesAsUint8Array,
type: 'text'
});
parseMp4TextSegment(segment, tracks.text.codec, doneFn);
return;
}

const trackInfo = {
isFmp4: true,
Expand Down
39 changes: 39 additions & 0 deletions src/transmuxer-worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import {Transmuxer} from 'mux.js/lib/mp4/transmuxer';
import CaptionParser from 'mux.js/lib/mp4/caption-parser';
import WebVttParser from 'mux.js/lib/mp4/webvtt-parser';
import mp4probe from 'mux.js/lib/mp4/probe';
import tsInspector from 'mux.js/lib/tools/ts-inspector.js';
import {
Expand Down Expand Up @@ -207,6 +208,44 @@ class MessageHandlers {
}, [segment.buffer]);
}

/**
* Initializes the WebVttParser and passes the init segment.
*
* @param {Uint8Array} data mp4 boxed WebVTT init segment data
*/
initMp4WebVttParser(data) {
if (!this.webVttParser) {
this.webVttParser = new WebVttParser();
}
const segment = new Uint8Array(data.data, data.byteOffset, data.byteLength);

// Set the timescale for the parser.
// This can be called repeatedly in order to set and re-set the timescale.
this.webVttParser.init(segment);
}

/**
* Parse an mp4 encapsulated WebVTT segment and return an array of cues.
*
* @param {Uint8Array} data a text/webvtt segment
* @return {Object[]} an array of parsed cue objects
*/
getMp4WebVttText(data) {
if (!this.webVttParser) {
// timescale might not be set yet if the parser is created before an init segment is passed.
// default timescale is 90k.
this.webVttParser = new WebVttParser();
}
const segment = new Uint8Array(data.data, data.byteOffset, data.byteLength);
const parsed = this.webVttParser.parseSegment(segment);

this.self.postMessage({
action: 'getMp4WebVttText',
mp4VttCues: parsed || [],
data: segment.buffer
}, [segment.buffer]);
}

probeMp4StartTime({timescales, data}) {
const startTime = mp4probe.startTime(timescales, data);

Expand Down
74 changes: 58 additions & 16 deletions src/vtt-segment-loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,6 @@ export default class VTTSegmentLoader extends SegmentLoader {
this.shouldSaveSegmentTimingInfo_ = false;
}

createTransmuxer_() {
// don't need to transmux any subtitles
return null;
}

/**
* Indicates which time ranges are buffered
*
Expand Down Expand Up @@ -282,6 +277,11 @@ export default class VTTSegmentLoader extends SegmentLoader {
}

const segmentInfo = this.pendingSegment_;
const isMp4WebVttSegmentWithCues = result.mp4VttCues && result.mp4VttCues.length;

if (isMp4WebVttSegmentWithCues) {
segmentInfo.mp4VttCues = result.mp4VttCues;
}

// although the VTT segment loader bandwidth isn't really used, it's good to
// maintain functionality between segment loaders
Expand Down Expand Up @@ -334,11 +334,13 @@ export default class VTTSegmentLoader extends SegmentLoader {
return;
}

this.updateTimeMapping_(
segmentInfo,
this.syncController_.timelines[segmentInfo.timeline],
this.playlist_
);
if (!isMp4WebVttSegmentWithCues) {
this.updateTimeMapping_(
segmentInfo,
this.syncController_.timelines[segmentInfo.timeline],
this.playlist_
);
}

if (segmentInfo.cues.length) {
segmentInfo.timingInfo = {
Expand Down Expand Up @@ -380,14 +382,49 @@ export default class VTTSegmentLoader extends SegmentLoader {
this.handleAppendsDone_();
}

handleData_() {
// noop as we shouldn't be getting video/audio data captions
// that we do not support here.
handleData_(simpleSegment, result) {
const isVttType = simpleSegment && simpleSegment.type === 'vtt';
const isTextResult = result && result.type === 'text';
const isFmp4VttSegment = isVttType && isTextResult;
// handle segment data for fmp4 encapsulated webvtt

if (isFmp4VttSegment) {
super.handleData_(simpleSegment, result);
}
}

updateTimingInfoEnd_() {
// noop
}

/**
* Utility function for converting mp4 webvtt cue objects into VTTCues.
*
* @param {Object} segmentInfo with mp4 webvtt cues for parsing into VTTCue objecs
*/
parseMp4VttCues_(segmentInfo) {
const timestampOffset = this.sourceUpdater_.videoTimestampOffset() === null ?
this.sourceUpdater_.audioTimestampOffset() :
this.sourceUpdater_.videoTimestampOffset();

segmentInfo.mp4VttCues.forEach((cue) => {
const start = cue.start + timestampOffset;
const end = cue.end + timestampOffset;
const vttCue = new window.VTTCue(start, end, cue.cueText);

if (cue.settings) {
cue.settings.split(' ').forEach((cueSetting) => {
const keyValString = cueSetting.split(':');
const key = keyValString[0];
const value = keyValString[1];

vttCue[key] = isNaN(value) ? value : Number(value);
});
}
segmentInfo.cues.push(vttCue);
});
}

/**
* Uses the WebVTT parser to parse the segment response
*
Expand All @@ -406,6 +443,14 @@ export default class VTTSegmentLoader extends SegmentLoader {
throw new NoVttJsError();
}

segmentInfo.cues = [];
segmentInfo.timestampmap = { MPEGTS: 0, LOCAL: 0 };

if (segmentInfo.mp4VttCues) {
this.parseMp4VttCues_(segmentInfo);
return;
}

if (typeof window.TextDecoder === 'function') {
decoder = new window.TextDecoder('utf8');
} else {
Expand All @@ -419,9 +464,6 @@ export default class VTTSegmentLoader extends SegmentLoader {
decoder
);

segmentInfo.cues = [];
segmentInfo.timestampmap = { MPEGTS: 0, LOCAL: 0 };

parser.oncue = segmentInfo.cues.push.bind(segmentInfo.cues);
parser.ontimestampmap = (map) => {
segmentInfo.timestampmap = map;
Expand Down
Loading

0 comments on commit 4870874

Please sign in to comment.