Hls.js
HLS.js is a JavaScript library that plays HLS in browsers with support for MSE.
Events
MANIFEST_PARSED
fired after manifest has been parsed.
onManifestParsed(event, data) {
// index of first quality level appearing in Manifest.
const firstLevel = data.firstLevel;
if (firstLevel >= data.levels.length) {
const msg1 = `Overflow firstLevel(${firstLevel}) index, `;
const msg2 = `levels.length(${data.levels.length})`;
throw new IllegalStateException(msg1 + msg2);
}
const level = data.levels[firstLevel];
const levelDetails = level.details;
if (!levelDetails) {
return;
}
this.fragments = levelDetails.fragments;
this.totalDuration = levelDetails.totalduration;
const count = this.fragments.length;
console.debug(`Total fragments count=${count},totalDuration=${this.totalDuration}`);
}
FRAG_CHANGED
fired when fragment matching with current video position is changing.
onFragChanged(event, data) {
this.currentFragment = data.frag;
if (!this.currentFragment) {
throw new UndefinedException('Current fragment is undefined or null');
}
const sn = this.currentFragment.sn;
console.debug(`Change current fragment: #${sn}`);
if (!this.currentFragment.relurl) {
throw new EmptyException('Not exists fragment url');
}
// this.currentFragmentMoment = parseFragmentDateTime(this.currentFragment.relurl);
// this.currentFragmentVideoTime = this.video.currentTime;
// const ct = this.video.currentTime;
// const d = this.video.duration;
// console.debug(`Video information: ct=${ct},d=${d}`);
}
그 밖의 주요 이벤트
사용하진 않았지만...
// hls.on(Hls.Events.ERROR, (e,d) => {console.debug(e,d);});
// hls.on(Hls.Events.FRAG_BUFFERED, (e,d) => {console.debug(e,d);});
// hls.on(Hls.Events.FRAG_LOADING, (e,d) => {console.debug(e,d);});
// hls.on(Hls.Events.FRAG_LOADED, (e,d) => {console.debug(e,d);});
// hls.on(Hls.Events.FRAG_PARSED, (e,d) => {console.debug(e,d);});
// hls.on(Hls.Events.BUFFER_APPENDED, (e,d) => {console.debug(e,d);});
// hls.on(Hls.Events.BUFFER_EOS, (e,d) => {console.debug(e,d);});
// hls.on(Hls.Events.LEVEL_PTS_UPDATED, (e,d) => {console.debug(e,d);});
Set up token authentication
- Github - Azure-Samples/media-services-3rdparty-player-samples/docs/hls.js/how-to-hls-js-player.md
- Github - hls.js - API - xhrsetup
// append authorization header for encryption token
Object.assign(config, {
xhrSetup: (xhr, url) => {
const encryptionKeyUrl = 'ENCRYPTION KEY URL'
// check for encryption key url and append only for that xhr
if (encryptionKeyUrl !== url) {
return
}
xhr.setRequestHeader('Authorization', 'Bearer=' + token)
}
})
// append authorization header for DRM token
Object.assign(config, {
licenseXhrSetup: (xhr, url) => {
xhr.setRequestHeader('Authorization', 'Bearer=' + token)
}
})
updateRecordRanges
획득한 Fragment 목록을 사용하여 비디오의 재생 범위를 계산하는 방법. recc의 HlsPlayer
에서 사용되었다.
const ONE_SECOND_TO_MILLISECONDS = 1000;
const FRAGMENT_DATETIME_REGEX = /(\d+)-(\d+)-(\d+)\/(\d+)_(\d+)_(\d+)\.(\d+)\.ts$/;
function parseFragmentDateTime(url: string) {
const match = FRAGMENT_DATETIME_REGEX.exec(url);
if (!match) {
throw new IllegalStateException('The URL `Fragment` is malformed');
}
const year = match[1];
const month = match[2];
const day = match[3];
const hour = match[4];
const minute = match[5];
const second = match[6];
const micro = match[7];
const time = `${year}-${month}-${day}T${hour}:${minute}:${second}.${micro}`;
return moment(time);
}
onManifestParsed(event, data) {
// index of first quality level appearing in Manifest.
const firstLevel = data.firstLevel;
if (firstLevel >= data.levels.length) {
const msg1 = `Overflow firstLevel(${firstLevel}) index, `;
const msg2 = `levels.length(${data.levels.length})`;
throw new IllegalStateException(msg1 + msg2);
}
const level = data.levels[firstLevel];
const levelDetails = level.details;
if (!levelDetails) {
return;
}
this.fragments = levelDetails.fragments;
this.totalDuration = levelDetails.totalduration;
const count = this.fragments.length;
console.debug(`Total fragments count=${count},totalDuration=${this.totalDuration}`);
this.updateRecordRanges(this.fragments);
const body = {
date: this.date,
device_uid: this.deviceNumber,
} as VmsFilterEventQ;
this.$api2.postVmsEventsFilter(this.group, this.project, body)
.then(items => {
this.events = items;
console.debug(`Total events count: ${this.events.length}`);
this.updateEventRanges(this.events);
})
.catch(error => {
this.events = [];
});
}
updateRecordRanges(fragments: Array<Fragment>) {
if (fragments.length === 0) {
throw new EmptyException('Not exists fragments');
}
const resolution = 2 * ONE_SECOND_TO_MILLISECONDS;
const result = [] as Array<RatioRange>;
let prevStart: undefined | moment.Moment;
let prevLast: undefined | moment.Moment;
let mergedCount = 0;
for (let i = 0; i < fragments.length; ++i) {
const f = fragments[i];
if (!f.relurl) {
throw new EmptyException('Not exists fragment url');
}
const start = parseFragmentDateTime(f.relurl);
const durationMilliseconds = Math.round(f.duration * ONE_SECOND_TO_MILLISECONDS);
const last = start.clone().add(durationMilliseconds, 'milliseconds');
if (prevStart && prevLast) {
const diff = start.diff(prevLast, 'milliseconds')
const absDiff = Math.abs(diff);
if (absDiff < resolution) {
// This is a `continuous` fragment.
prevLast = last;
mergedCount++;
} else {
// Flush the previous `range`.
result.push({
start: prevStart.valueOf(),
last: prevLast.valueOf(),
count: mergedCount,
} as RatioRange);
// New beginning.
prevStart = start;
prevLast = last;
mergedCount = 1;
}
} else {
// First beginning.
prevStart = start;
prevLast = last;
mergedCount = 1;
}
}
// Last flush.
if (prevStart && prevLast) {
result.push({
start: prevStart.valueOf(),
last: prevLast.valueOf(),
count: mergedCount,
} as RatioRange);
}
const beginDateText = `${this.date}T00:00:00`;
const begin = moment(beginDateText).valueOf();
this.recordRanges = result.map(x => {
return {
start: (x.start - begin) / MILLISECONDS_TO_DAY,
last: (x.last - begin) / MILLISECONDS_TO_DAY,
count: x.count,
};
})
}
video.currentTime
// absTimeToCurrentMilliseconds(time: moment.Moment) {
// const findIndex = this.findNearestRatioRangeIndex(time);
// let totalMilliseconds = 0.0;
// for (let i = 0; i < findIndex; ++i) {
// const range = this.recordRanges[i];
// const startValue = range.startTime.valueOf();
// const lastValue = range.lastTime.valueOf();
// console.assert(lastValue >= startValue);
// totalMilliseconds += (lastValue - startValue);
// }
//
// const timeValue = time.valueOf();
// const range = this.recordRanges[findIndex];
// const startValue = range.startTime.valueOf();
// const lastValue = range.lastTime.valueOf();
// if (timeValue < startValue) {
// return totalMilliseconds;
// } else if (lastValue < timeValue) {
// return totalMilliseconds + (lastValue - startValue);
// } else {
// console.assert(startValue <= timeValue && timeValue <= lastValue);
// return totalMilliseconds + (timeValue - startValue);
// }
// }
//
// currentMillisecondsToAbsTime(currentMilliseconds: number) {
// let remainMilliseconds = currentMilliseconds;
// let findIndex = 0;
// for (; findIndex < this.recordRanges.length; ++findIndex) {
// const range = this.recordRanges[findIndex];
// const startValue = range.startTime.valueOf();
// const lastValue = range.lastTime.valueOf();
// console.assert(lastValue >= startValue);
// const durationMilliseconds = lastValue - startValue;
//
// if (durationMilliseconds >= remainMilliseconds) {
// break;
// }
// remainMilliseconds -= durationMilliseconds;
// }
//
// const range = this.recordRanges[findIndex];
// const start = range.startTime;
// const result = start.clone();
// result.add(remainMilliseconds, 'milliseconds')
// return result;
// }
Example
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Demo AV Muxing</title>
</head>
<body>
<video id="video" loop playsinline autoplay muted controls></video>
<script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
<script>
var video = document.getElementById('video');
var videoSrc = './live_stream.m3u8';
if (Hls.isSupported()) {
var hls = new Hls();
hls.loadSource(videoSrc);
hls.attachMedia(video);
console.debug("1")
hls.on(Hls.Events.MANIFEST_PARSED, () => {
video.muted = 'muted';
video.autoplay = 'autoplay';
video.playsinline = 'true';
video.play();
console.debug("2")
});
} else {
alert('Unsupported HLS.js component');
}
</script>
</body>
</html>