265 lines
9.0 KiB
JavaScript
265 lines
9.0 KiB
JavaScript
|
// IPFS config
|
||
|
var ipfs_gateway = '__IPFS_GATEWAY__'; // IPFS gateway
|
||
|
|
||
|
// Live stream config
|
||
|
var m3u8_ipfs = 'live.m3u8'; // HTTP or local path to m3u8 file containing IPFS content
|
||
|
//m3u8_ipfs = '__IPFS_GATEWAY__/ipns/__IPFS_ID_ORIGIN__'; // IPNS path to m3u8 file containing IPFS content (uncomment to enable)
|
||
|
var m3u8_http_urls = [__M3U8_HTTP_URLS__]; // HTTP or local paths to m3u8 file containing HTTP content (optional)
|
||
|
|
||
|
// Video sharing links config
|
||
|
var date = new Date().toLocaleDateString("en-CA", {timeZone: "America/Toronto"}); // Current date (default to American/Toronto)
|
||
|
var rootURL = window.location.href.split('?')[0]; // Root URL used in sharing links
|
||
|
|
||
|
// Process URL params
|
||
|
function getURLParam(key) {
|
||
|
return new URLSearchParams(window.location.search).get(key);
|
||
|
}
|
||
|
|
||
|
var ipfs_gw = getURLParam('gw'); // Set custom IPFS gateway
|
||
|
if (getURLParam('m3u8'))
|
||
|
var m3u8_ipfs = getURLParam('m3u8'); // Set m3u8 file URL to override IPFS live stream
|
||
|
var vod_ipfs = getURLParam('vod') || getURLParam('ipfs'); // Set IPFS content hash of mp4 file to play IPFS on-demand video stream ('ipfs' for backward compatability)
|
||
|
var start_from = getURLParam("from"); // Set IPFS content hash or timecode to start video playback from
|
||
|
|
||
|
// Configure default playback behaviour
|
||
|
var stream_type = 'application/x-mpegURL'; // Type of video stream
|
||
|
var stream_url_ipfs = m3u8_ipfs; // Source of IPFS video stream
|
||
|
var stream_urls_http = m3u8_http_urls; // Source of HTTP video stream
|
||
|
|
||
|
if (ipfs_gw) {
|
||
|
ipfs_gateway = ipfs_gw;
|
||
|
}
|
||
|
|
||
|
if (vod_ipfs) {
|
||
|
stream_type = 'video/mp4';
|
||
|
stream_url_ipfs = ipfs_gateway + '/ipfs/' + vod_ipfs;
|
||
|
stream_urls_http = [];
|
||
|
document.getElementById('selectingTitle').innerHTML = 'Select recorded stream source';
|
||
|
}
|
||
|
|
||
|
// If start_from is not a number it's probably an IPFS hash so calculate to correct start_from
|
||
|
var hash="";
|
||
|
if (start_from && +start_from != start_from) {
|
||
|
hash = start_from;
|
||
|
// Remove start_from value since the hash may not be in the list
|
||
|
start_from = undefined;
|
||
|
var xmlhttp = new XMLHttpRequest();
|
||
|
xmlhttp.onreadystatechange = function () {
|
||
|
if (this.readyState == 4 && this.status == 200) {
|
||
|
file = this.response;
|
||
|
fileline = file.split("\n");
|
||
|
counter = 0;
|
||
|
// Loop through entries in the file
|
||
|
for (var a = 0; a < fileline.length; a++) {
|
||
|
// Look for EXTINF tags that describe the length of the chunk
|
||
|
if (fileline[a].indexOf("EXTINF:") > 0) {
|
||
|
// Parse out the length of the chunk
|
||
|
var number = fileline[a].substring(fileline[a].indexOf("EXTINF:") + 7);
|
||
|
number = number.substring(0, number.length - 1);
|
||
|
// Skip over chunk hash information
|
||
|
a++;
|
||
|
if (fileline[a].indexOf(hash) > 0) {
|
||
|
// If hash is found set the start_from to the counter and exit;
|
||
|
start_from = counter;
|
||
|
return;
|
||
|
}
|
||
|
// Add chunk length to counter
|
||
|
counter = counter + parseFloat(number);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
xmlhttp.open("GET", m3u8_ipfs, true);
|
||
|
xmlhttp.send();
|
||
|
}
|
||
|
|
||
|
// Function to get hash from timeindex
|
||
|
function getHashFromTime(timeindex) {
|
||
|
var xmlhttp = new XMLHttpRequest();
|
||
|
xmlhttp.open("GET", m3u8_ipfs, false);
|
||
|
xmlhttp.send();
|
||
|
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
|
||
|
file = xmlhttp.response;
|
||
|
fileline = file.split("\n");
|
||
|
counter = 0;
|
||
|
hash = "";
|
||
|
// Loop through entries in the file
|
||
|
for (var a = 0; a < fileline.length; a++) {
|
||
|
// Look for EXTINF tags that describe the length of the chunk
|
||
|
if (fileline[a].indexOf("EXTINF:") > 0) {
|
||
|
// Parse out the length of the chunk
|
||
|
var number = fileline[a].substring(fileline[a].indexOf("EXTINF:") + 7);
|
||
|
number = number.substring(0, number.length - 1);
|
||
|
counter = counter + parseFloat(number);
|
||
|
|
||
|
// Parse out current hash
|
||
|
var hash = fileline[a+1].substring(fileline[a+1].lastIndexOf("/") + 1);
|
||
|
|
||
|
// Check if the current counter is larger than timeindex requested
|
||
|
if (counter > timeindex) return hash;
|
||
|
|
||
|
// Skip over chunk hash information
|
||
|
a++;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return "";
|
||
|
}
|
||
|
|
||
|
// Configure video player
|
||
|
var live = videojs('live', { liveui: true });
|
||
|
|
||
|
// Override native player for platform and browser consistency
|
||
|
videojs.options.html5.nativeAudioTracks = false;
|
||
|
videojs.options.html5.nativeVideoTracks = false;
|
||
|
videojs.options.hls.overrideNative = true;
|
||
|
|
||
|
function httpStream() {
|
||
|
live.src({
|
||
|
src: stream_urls_http[Math.floor(Math.random() * m3u8_http_urls.length)],
|
||
|
type: stream_type
|
||
|
});
|
||
|
loadStream();
|
||
|
}
|
||
|
|
||
|
// Counter to track video playback state
|
||
|
var streamState = 0;
|
||
|
|
||
|
function ipfsStream() {
|
||
|
live.src({
|
||
|
src: stream_url_ipfs,
|
||
|
type: stream_type
|
||
|
});
|
||
|
loadStream();
|
||
|
|
||
|
// Start playback from timecode if exists
|
||
|
if (vod_ipfs && start_from && +start_from == start_from) {
|
||
|
setTimeout(function() {
|
||
|
live.currentTime(start_from);
|
||
|
}, 1);
|
||
|
}
|
||
|
|
||
|
videojs.Hls.xhr.beforeRequest = function(options) {
|
||
|
|
||
|
// When .m3u8 is loaded, start playback and transition to streamState = 1
|
||
|
if (options.uri.indexOf('.m3u8') > 0) {
|
||
|
if (!streamState) {
|
||
|
live.play();
|
||
|
streamState = 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (options.uri.indexOf('/ipfs/') > 0) {
|
||
|
document.getElementById('loadingTitle').innerHTML = 'Located stream via IPFS';
|
||
|
document.getElementById('msg').innerHTML = 'Downloading video content...';
|
||
|
// Use specified IPFS gateway by replacing it in the uri
|
||
|
options.uri = ipfs_gateway + options.uri.substring(options.uri.indexOf('/ipfs/'));
|
||
|
|
||
|
// Wait for two .ts chunks to be loaded before applying seek action
|
||
|
if (streamState < 3) {
|
||
|
streamState++;
|
||
|
if (streamState == 3) {
|
||
|
if (!start_from) {
|
||
|
// Seek to live after waiting 1 s
|
||
|
setTimeout(function() { live.liveTracker.seekToLiveEdge(); }, 1);
|
||
|
} else {
|
||
|
// Seek to start_from time after waiting 1 s
|
||
|
setTimeout(function() { live.currentTime(start_from); }, 1);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (options.uri.indexOf('/ipns/') > 0) {
|
||
|
document.getElementById('loadingTitle').innerHTML = 'Located stream via IPFS';
|
||
|
document.getElementById('msg').innerHTML = 'Downloading video content...';
|
||
|
options.uri = ipfs_gateway + options.uri.substring(options.uri.indexOf('/ipns/'));
|
||
|
}
|
||
|
console.debug(options.uri);
|
||
|
return options;
|
||
|
};
|
||
|
}
|
||
|
|
||
|
function loadStream() {
|
||
|
document.getElementById('loadingStream').style.display = 'block';
|
||
|
document.getElementById('selectStream').style.display = 'none';
|
||
|
}
|
||
|
|
||
|
document.querySelector('.ipfs-stream').addEventListener('click', function(event) {
|
||
|
ipfsStream();
|
||
|
});
|
||
|
|
||
|
document.querySelector('.http-stream').addEventListener('click', function(event) {
|
||
|
httpStream();
|
||
|
});
|
||
|
|
||
|
live.metadata = 'none';
|
||
|
|
||
|
live.on('loadedmetadata', function() {
|
||
|
document.getElementById('streamSelector').style.display = 'none';
|
||
|
});
|
||
|
|
||
|
live.on('loadeddata', function(event) {
|
||
|
console.debug(event);
|
||
|
});
|
||
|
|
||
|
var refreshButton = document.createElement('button');
|
||
|
refreshButton.className = 'button button-primary compact stream-refresh';
|
||
|
refreshButton.innerHTML = 'Refresh page and try again';
|
||
|
refreshButton.addEventListener('click', function() {
|
||
|
window.location.reload(true);
|
||
|
});
|
||
|
|
||
|
live.on('error', function(event) {
|
||
|
console.debug(this.error());
|
||
|
document.getElementById('loadingTitle').innerHTML = 'Unable to load video stream';
|
||
|
document.querySelector('.loader-animation').style.display = 'none';
|
||
|
document.getElementById('msg').innerHTML = this.error().message;
|
||
|
document.getElementById('loadingStream').appendChild(refreshButton);
|
||
|
});
|
||
|
|
||
|
if (!stream_urls_http || !Array.isArray(stream_urls_http) || (stream_urls_http.length === 0)) {
|
||
|
document.querySelector('.http-stream').setAttribute('disabled', 'disabled');
|
||
|
}
|
||
|
|
||
|
// Video sharing links
|
||
|
function getShareLink(key) {
|
||
|
if (vod_ipfs) {
|
||
|
return `${rootURL}?vod=${vod_ipfs}&from=${live.currentTime()}`;
|
||
|
}
|
||
|
var m3u8 = getURLParam('m3u8');
|
||
|
if (!m3u8) {
|
||
|
m3u8 = `live-${date}.m3u8`;
|
||
|
}
|
||
|
var bookmark = getHashFromTime(live.currentTime());
|
||
|
return `${rootURL}?m3u8=${m3u8}&from=${bookmark}`;
|
||
|
}
|
||
|
|
||
|
setInterval(function () {
|
||
|
var link = document.getElementById('link');
|
||
|
link.value = getShareLink();
|
||
|
}, 5000);
|
||
|
|
||
|
var shareTweet = document.querySelector('.share-tweet');
|
||
|
var shareLink = document.querySelector('.share-link');
|
||
|
|
||
|
if (shareTweet) {
|
||
|
shareTweet.addEventListener('click', function() {
|
||
|
var link = document.getElementById('link');
|
||
|
link.value = getShareLink();
|
||
|
const tweetURL = link.value;
|
||
|
window.open(`https://twitter.com/intent/tweet?url=${encodeURIComponent(tweetURL)}`);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
if (shareLink) {
|
||
|
shareLink.addEventListener('click', function() {
|
||
|
var link = document.getElementById('link');
|
||
|
link.value = getShareLink();
|
||
|
link.select();
|
||
|
link.setSelectionRange(0, 99999); // For mobile devices
|
||
|
document.execCommand('copy');
|
||
|
alert('Link copied to clipboard');
|
||
|
});
|
||
|
}
|