feat: Add Be and tbd skill, also added Roadmap file

This commit is contained in:
2026-05-10 16:32:12 -04:00
parent 3500ade13f
commit 0bb8885802
29587 changed files with 10611695 additions and 0 deletions

View File

@@ -0,0 +1,148 @@
<html>
<head>
<style>
body {
background-color: #cccccc;
}
#log {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
margin: 0;
color: #ff0000;
overflow: hidden;
font-size: 20px;
z-index: 999;
}
#log .inner {
position: absolute;
bottom: 0;
left: 0;
right: 0;
margin: 0;
padding: 0;
background-color: rgba(255, 255, 255, 0.75);
}
#log .inner .line {
margin: 0;
padding: 3px 15px;
border-width: 1px;
border-color: #aaaaaa;
border-style: none;
border-bottom-style: solid;
white-space: pre-wrap;
}
</style>
<script src="/dist/hls.min.js"></script>
<script>
var video, hls, logString ='';
function startStream(streamUrl, callback) {
if (Hls.isSupported()) {
if (hls) {
callback({ code : 'hlsjsAlreadyInitialised', logs : logString});
return;
}
video = document.getElementById('video');
try {
hls = new Hls({debug: true});
console.log(navigator.userAgent);
hls.loadSource(streamUrl);
hls.attachMedia(video);
hls.on(Hls.Events.MANIFEST_PARSED, function() {
video.play();
});
hls.on(Hls.Events.ERROR, function(event, data) {
if (data.fatal) {
console.log('hlsjs fatal error :' + data.details);
if (data.details === Hls.ErrorDetails.INTERNAL_EXCEPTION) {
console.log('exception in :' + data.event);
console.log(data.err.stack ? JSON.stringify(data.err.stack) : data.err.message);
}
callback({ code : data.details, logs : logString});
}
});
video.onerror = function(event) {
console.log('video error, code :' + video.error.code);
callback({ code : 'video_error_' + video.error.code, logs : logString});
};
} catch(err) {
callback({ code : 'exception', logs : logString});
}
} else {
callback({ code : 'notSupported', logs : logString});
}
}
function switchToLowestLevel(mode) {
switch(mode) {
case 'current':
hls.currentLevel = 0;
break;
case 'next':
hls.nextLevel = 0;
break;
case 'load':
default:
hls.loadLevel = 0;
break;
}
}
function switchToHighestLevel() {
var highestLevel = hls.levels.length-1;
switch(mode) {
case 'current':
hls.currentLevel = highestLevel;
break;
case 'next':
hls.nextLevel = highestLevel;
break;
case 'load':
default:
hls.loadLevel = highestLevel;
break;
}
}
</script>
</head>
<body id="hlsjs-functional-tests">
<video id="video"></video>
<div id="log">
<div class="inner"></div>
</div>
<script>
(function() {
var methods = ["log", "debug", "info", "warn", "error"];
methods.forEach(function(methodName) {
var original = console[methodName];
if (!original) {
return;
}
console[methodName] = function() {
append(methodName, Array.prototype.slice.call(arguments).map(JSON.stringify).join(" "));
return original.apply(this, arguments);
};
});
var log = document.getElementById('log');
var inner = log.getElementsByClassName('inner')[0];
function append(methodName, msg) {
var a = (new Date()).toISOString().replace("T", " ").replace("Z", "")+': '+msg;
var text = document.createTextNode(a);
var line = document.createElement('pre');
line.className = "line line-"+methodName;
line.appendChild(text);
logString += a + '\n';
inner.appendChild(line);
}
})();
</script>
</body>
</html>

View File

@@ -0,0 +1,228 @@
var assert = require('assert');
var webdriver = require('selenium-webdriver');
// requiring this automatically adds the chromedriver binary to the PATH
var chromedriver = require('chromedriver');
var HttpServer = require('http-server');
var streams = require('../streams.json');
function retry(cb, numAttempts, interval) {
numAttempts = numAttempts || 20;
interval = interval || 3000;
return new Promise(function(resolve, reject) {
var attempts = 0;
attempt();
function attempt() {
cb().then(function(res) {
resolve(res);
}).catch(function(e) {
if (++attempts >= numAttempts) {
// reject with the last error
reject(e);
}
else {
setTimeout(attempt, interval);
}
});
}
});
}
var onTravis = !!process.env.TRAVIS;
HttpServer.createServer({
showDir: false,
autoIndex: false,
root: './',
}).listen(8000, '127.0.0.1');
var browserConfig = {version : 'latest'};
if (onTravis) {
var UA_VERSION = process.env.UA_VERSION;
if (UA_VERSION) {
browserConfig.version = UA_VERSION;
}
var UA = process.env.UA;
if (!UA) {
throw new Error('No test browser name.')
}
var OS = process.env.OS;
if (!OS) {
throw new Error('No test browser platform.')
}
browserConfig.name = UA;
browserConfig.platform = OS;
}
else {
browserConfig.name = "chrome";
}
var browserDescription = browserConfig.name;
if (browserConfig.version) {
browserDescription += ' ('+browserConfig.version+')';
}
if (browserConfig.platform) {
browserDescription += ', '+browserConfig.platform;
}
describe('testing hls.js playback in the browser on "'+browserDescription+'"', function() {
beforeEach(function() {
var capabilities = {
name: '"'+stream.description+'" on "'+browserDescription+'"',
browserName: browserConfig.name,
platform: browserConfig.platform,
version: browserConfig.version,
commandTimeout: 90,
};
if (onTravis) {
capabilities['tunnel-identifier'] = process.env.TRAVIS_JOB_NUMBER;
capabilities.build = 'HLSJS-'+process.env.TRAVIS_BUILD_NUMBER;
capabilities.username = process.env.SAUCE_USERNAME;
capabilities.accessKey = process.env.SAUCE_ACCESS_KEY;
this.browser = new webdriver.Builder().usingServer('http://'+process.env.SAUCE_USERNAME+':'+process.env.SAUCE_ACCESS_KEY+'@ondemand.saucelabs.com:80/wd/hub');
}
else {
this.browser = new webdriver.Builder();
}
this.browser = this.browser.withCapabilities(capabilities).build();
this.browser.manage().timeouts().setScriptTimeout(75000);
console.log("Retrieving web driver session...");
return this.browser.getSession().then(function(session) {
console.log("Web driver session id: "+session.getId());
if (onTravis) {
console.log("Job URL: https://saucelabs.com/jobs/"+session.getId());
}
return retry(function() {
console.log("Loading test page...");
return this.browser.get('http://127.0.0.1:8000/tests/functional/auto/hlsjs.html').then(function() {
// ensure that the page has loaded and we haven't got an error page
return this.browser.findElement(webdriver.By.css('body#hlsjs-functional-tests')).catch(function(e) {
console.log("CSS not found");
this.browser.getPageSource().then(function(source){
console.log(source);
return Promise.reject(e);
});
}.bind(this));
}.bind(this));
}.bind(this)).then(function() {
console.log("Test page loaded.");
});
}.bind(this), function(err) {
console.log('error while Retrieving browser session:' + err);
});
});
afterEach(function() {
var browser = this.browser;
browser.executeScript('return logString').then(function(return_value){
console.log('travis_fold:start:debug_logs');
console.log('logs');
console.log(return_value);
console.log('travis_fold:end:debug_logs');
console.log("Quitting browser...");
return browser.quit().then(function() {
console.log("Browser quit.");
});
});
});
const testLoadedData = function(url) {
return function() {
return this.browser.executeAsyncScript(function(url) {
var callback = arguments[arguments.length - 1];
startStream(url, callback);
video.onloadeddata = function() {
callback({ code : 'loadeddata', logs : logString});
};
}, url).then(function(result) {
assert.strictEqual(result.code, 'loadeddata');
});
}
}
const testSmoothSwitch = function(url) {
return function() {
return this.browser.executeAsyncScript(function(url) {
var callback = arguments[arguments.length - 1];
startStream(url, callback);
video.onloadeddata = function() {
switchToHighestLevel('next');
};
window.setTimeout(function() {
callback({ code : video.readyState, logs : logString});
}, 12000);
}, url).then(function(result) {
assert.strictEqual(result.code, 4);
});
}
}
const testSeekOnLive = function(url) {
return function() {
return this.browser.executeAsyncScript(function(url) {
var callback = arguments[arguments.length - 1];
startStream(url, callback);
video.onloadeddata = function() {
window.setTimeout(function() { video.currentTime = video.duration - 5;}, 5000);
};
video.onseeked = function() {
callback({ code : 'seeked', logs : logString});
};
}, url).then(function(result) {
assert.strictEqual(result.code, 'seeked');
});
}
}
const testSeekOnVOD = function(url) {
return function() {
return this.browser.executeAsyncScript(function(url) {
var callback = arguments[arguments.length - 1];
startStream(url, callback);
video.onloadeddata = function() {
window.setTimeout(function() { video.currentTime = video.duration - 5;}, 5000);
};
video.onended = function() {
callback({ code : 'ended', logs : logString});
};
}, url).then(function(result) {
assert.strictEqual(result.code, 'ended');
});
}
}
const testSeekEndVOD = function(url) {
return function() {
return this.browser.executeAsyncScript(function(url) {
var callback = arguments[arguments.length - 1];
startStream(url, callback);
video.onloadeddata = function() {
window.setTimeout(function() { video.currentTime = video.duration;}, 5000);
};
video.onended = function() {
callback({ code : 'ended', logs : logString});
};
}, url).then(function(result) {
assert.strictEqual(result.code, 'ended');
});
}
}
for (var name in streams) {
var stream = streams[name];
var url = stream.url;
if (!stream.blacklist_ua || stream.blacklist_ua.indexOf(browserConfig.name) === -1) {
it('should receive video loadeddata event for ' + stream.description, testLoadedData(url));
if (stream.abr) {
it('should "smooth switch" to highest level and still play(readyState === 4) after 12s for ' + stream.description, testSmoothSwitch(url));
}
if (stream.live) {
it('should seek near the end and receive video seeked event for ' + stream.description, testSeekOnLive(url));
} else {
it('should seek 5s from end and receive video ended event for ' + stream.description, testSeekOnVOD(url));
//it('should seek on end and receive video ended event for ' + stream.description, testSeekEndVOD(url));
}
}
}
});

View File

@@ -0,0 +1,57 @@
<html>
<head>
<title>HLS.js Error Example</title>
</head>
<body>
<script src="../../../dist/hls.js"></script>
<video id="video" controls="controls" style="width:640px; height:360px; background:#000;"></video>
<script>
if (Hls.isSupported()) {
var video = document.getElementById('video');
var hls = new Hls({
debug : true,
maxBufferLength: 15,
maxBufferHole: 1,
maxSeekHole: 2,
capLevelToPlayerSize: true,
startFragPrefetch: false,
autoStartLoad: false,
manifestLoadingTimeOut: 10000,
manifestLoadingMaxRetry: 4,
manifestLoadingRetryDelay: 500,
enableCEA708Captions: true
});
hls.on(Hls.Events.ERROR, function(event, data) {
console.warn('ERROR', data);
});
function loadSource(url, time) {
function onAttached() {
hls.off(Hls.Events.MEDIA_ATTACHED, onAttached);
hls.on(Hls.Events.MANIFEST_PARSED, onParsed);
hls.loadSource(url);
}
function onParsed(event, data) {
hls.off(Hls.Events.MANIFEST_PARSED, onParsed);
hls.startLoad(time);
if (time !== undefined) {
video.currentTime = time;
}
video.play();
}
hls.detachMedia();
hls.on(Hls.Events.MEDIA_ATTACHED, onAttached);
hls.attachMedia(video);
}
}
</script>
<br />
<a href="#" onclick="loadSource('https://video-dev.github.io/streams/x36xhzz/x36xhzz.m3u8', 10)">Start stream one</a><br />
<a href="#" onclick="loadSource('http://www.streambox.fr/playlists/test_001/stream.m3u8', 10)">Start stream two</a><br />
<a href="#" onclick="loadSource('http://media.blacktrash.org/stsp.m3u8', 10)">Start stream three</a><br />
</body>
</html>

View File

@@ -0,0 +1,84 @@
<html>
<head>
<title>HLS.js Video tag hijack tester</title>
</head>
<body>
<!-- Swap script tags for dev/release -->
<script src="../../../dist/hls.js"></script>
<!--<script src="//localhost:8000/dist/hls.js"></script>-->
<video id="video" controls="controls" style="width:640px; height:360px; background:#000;"></video>
<div id="status">
Hijacking video tag in:
<span id="counter"></span>
</div>
<script>
(function() {
/**
* this page simulates hijacking of a video tag before destroying the existing one.
* sequence of events:
* 1. create the first instance (wait for playback)
* 2. allow 3 seconds of playback
* 3. create 2nd instance on a different stream
* 4. destroy the first instance
*/
const Hls = window.Hls;
if (!Hls.isSupported()) throw new Error('Hls.js is not supported');
const statusEl = document.querySelector('#status');
const counterEl = document.querySelector('#counter');
const video = document.querySelector('#video');
let hls1 = null;
// 1. create instance
initInstance1('http://www.streambox.fr/playlists/x31jrg1/x31jrg1.m3u8', function onPlaying() {
// 2. allow 3 seconds of playback
countDown(3000, counterEl, () => {
// 3. create another instance
initInstance2('https://nasa-i.akamaihd.net/hls/live/253565/NASA-NTV1-Public/master.m3u8');
statusEl.innerText = 'Hijacked!';
// 4. destroy the first instance
hls1.destroy();
});
});
// hoist line
function initInstance1(streamUrl, onPlaying) {
hls1 = new Hls({ debug: true });
hls1.loadSource(streamUrl);
hls1.attachMedia(video);
hls1.on(Hls.Events.MANIFEST_PARSED, function() {
video.play()
.then(() => onPlaying());
});
}
function initInstance2(streamUrl) {
console.log('------------------ Hijacking ------------------');
const hls = new Hls();
hls.loadSource(streamUrl);
hls.attachMedia(video);
hls.on(Hls.Events.MANIFEST_PARSED, function() {
video.play();
});
}
function countDown(time, el, cb) {
let startStamp = Date.now();
const interval = setInterval(() => {
const diff = Date.now() - startStamp;
if (diff < time) {
el.innerText = `${Math.round((time - diff) / 100) / 10}s`;
return;
}
clearInterval(interval);
cb();
}, 100);
}
}());
</script>
</body>
</html>

View File

@@ -0,0 +1,72 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>hls.js</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
.hlsjs {
position: relative;
width: 70%;
}
.ratio {
position: absolute;
padding-top: 75%;
}
video {
background-color: #ccc;
width: 100%;
}
</style>
<script src="../../../dist/hls.js"></script>
<script>
window.onload = function () {
if (Hls.isSupported()) {
var video1 = document.getElementById("video1"),
video2 = document.getElementById("video2"),
hls1 = new Hls({debug: true}),
hls2 = new Hls({debug: true});
hls1.on(Hls.Events.MEDIA_ATTACHED, function () {
hls1.loadSource("https://video-dev.github.io/streams/x36xhzz/x36xhzz.m3u8");
});
hls2.on(Hls.Events.MEDIA_ATTACHED, function () {
hls2.loadSource("http://www.streambox.fr/playlists/x31jrg1/x31jrg1.m3u8");
});
hls1.attachMedia(video1);
hls2.attachMedia(video2);
}
};
</script>
</head>
<body>
<h1>hls.js</h1>
<h2>First instance</h2>
<div class="hlsjs">
<video id="video1" controls></video>
<div class="ratio"></div>
</div>
<h2>Second instance</h2>
<div class="hlsjs">
<video id="video2" controls></video>
<div class="ratio"></div>
</div>
</body>
</html>

View File

@@ -0,0 +1,14 @@
{
"arte": {"url": "https://video-dev.github.io/streams/test_001/stream.m3u8", "description": "ARTE China,ABR", "live": false , "abr": true},
"bigBuckBunny480p": {"url": "https://video-dev.github.io/streams/x36xhzz/url_6/193039199_mp4_h264_aac_hq_7.m3u8", "description": "big buck bunny,480p", "live": false, "abr": false, "blacklist_ua" : ["internet explorer"]},
"deltatreDAI": {"url": "https://video-dev.github.io/streams/dai-discontinuity-deltatre/manifest.m3u8", "description": "Ad-insertion in event stream", "live": false, "abr": false, "blacklist_ua" : []},
"issue666": {"url": "http://www.streambox.fr/playlists/cisq0gim60007xzvi505emlxx.m3u8", "description": "hls.js/issues/666", "live": false, "abr": false,"blacklist_ua" : ["internet explorer"]},
"issue649": {"url": "http://cdn3.screen9.com/media/c/W/cW87csHkxsgu5TV1qs78aA_auto_hls.m3u8?auth=qlUjeCtbVdtkDfZYrtveTIVUXX1yuSqgF8wfWabzKpX72r-d5upW88-FHuyRRdnZA_1PKRTGAtTt_6Z-aj22kw", "description": "hls.js/issues/649", "live": false, "abr": false},
"nasa": {"url": "https://nasa-i.akamaihd.net/hls/live/253565/NASA-NTV1-Public/master.m3u8", "description": "NASA live stream", "live": true, "abr": false, "blacklist_ua" : ["internet explorer","safari"]},
"closedcaptions" : {"url": "http://playertest.longtailvideo.com/adaptive/captions/playlist.m3u8", "description": "CNN special report, with CC", "live": false, "abr": false, "blacklist_ua" : ["safari"]},
"oceans_aes": {"url": "http://playertest.longtailvideo.com/adaptive/oceans_aes/oceans_aes.m3u8", "description": "AES encrypted,ABR", "live": false , "abr": true},
"bbb_aes": {"url": "http://streambox.fr/playlists/sample_aes/index.m3u8", "description": "SAMPLE-AES encrypted", "live": false , "abr": false},
"mp3": {"url": "https://player.webvideocore.net/CL1olYogIrDWvwqiIKK7eLBkzvO18gwo9ERMzsyXzwt_t-ya8ygf2kQBZww38JJT/8i4vvznv8408.m3u8", "description": "MP3 VOD demo", "live": false , "abr": false, "blacklist_ua" : ["safari"]},
"mpeg_audio": {"url": "https://pl.streamingvideoprovider.com/mp3-playlist/playlist.m3u8", "description": "MPEG Audio Only demo", "live": false , "abr": false, "blacklist_ua" : ["internet explorer","MicrosoftEdge","safari","firefox"]},
"fmp4": {"url": "https://storage.googleapis.com/shaka-demo-assets/angel-one-hls/hls.m3u8", "description": "HLS fMP4 Angel One multiple audio tracks", "live": false , "abr": false, "blacklist_ua" : ["safari"]}
}