Skip to content

Commit

Permalink
Merge branch 'upstream_hls.js/master' into release
Browse files Browse the repository at this point in the history
* upstream_hls.js/master:
  Highlight selected demo tab, and auto-select first tab (Playback)
  Improve test logging
  Fix JavaScript functions executed in IE11 by Selenium
  Update Safari version used in functional tests
  Fix demo timeline event buffer reset event handler
  Prevent immediate level switch from performing unnecessary pause and seeks as this can interfere with 3rd party application state

# Conflicts:
#	demo/main.js
  • Loading branch information
Rob Walch committed Jul 27, 2020
2 parents 1457a0e + c4445b1 commit f98348d
Show file tree
Hide file tree
Showing 6 changed files with 66 additions and 71 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Expand Up @@ -68,7 +68,7 @@ jobs:
# - stage: testFuncOptional
# env: TRAVIS_MODE=funcTests UA=firefox OS="OS X 10.11"
- stage: testFuncOptional
env: TRAVIS_MODE=funcTests UA=safari OS="OS X 10.11" UA_VERSION="9.0"
env: TRAVIS_MODE=funcTests UA=safari OS="OS X 10.12" UA_VERSION="10.1"
addons:
sauce_connect:
tunnel_domains: localhost
Expand Down
24 changes: 12 additions & 12 deletions demo/index.html
Expand Up @@ -121,15 +121,15 @@ <h3>
<pre id="errorOut" class="center" style="white-space: pre-wrap;"></pre>

<div class="center" style="text-align: center;" id="toggleButtons">
<button type="button" class="btn btn-sm" onclick="toggleTab('timelineTab');">Timeline</button>
<button type="button" class="btn btn-sm" onclick="toggleTab('playbackControlTab');">Playback</button>
<button type="button" class="btn btn-sm" onclick="toggleTab('qualityLevelControlTab');">Quality-levels</button>
<button type="button" class="btn btn-sm" onclick="toggleTab('audioTrackControlTab');">Audio-tracks</button>
<button type="button" class="btn btn-sm" onclick="toggleTab('statsDisplayTab');">Buffer &amp; Statistics</button>
<button type="button" class="btn btn-sm" onclick="toggleTab('metricsDisplayTab'); showMetrics();">Real-time metrics</button>
<button type="button" class="btn btn-sm demo-tab-btn" data-tab="playbackControlTab" onclick="toggleTab(this);">Playback</button>
<button type="button" class="btn btn-sm demo-tab-btn" data-tab="qualityLevelControlTab" onclick="toggleTab(this);">Quality-levels</button>
<button type="button" class="btn btn-sm demo-tab-btn" data-tab="audioTrackControlTab" onclick="toggleTab(this);">Audio-tracks</button>
<button type="button" class="btn btn-sm demo-tab-btn" data-tab="timelineTab" onclick="toggleTab(this);">Timeline</button>
<button type="button" class="btn btn-sm demo-tab-btn" data-tab="statsDisplayTab" onclick="toggleTab(this);">Buffer &amp; Statistics</button>
<button type="button" class="btn btn-sm demo-tab-btn" data-tab="metricsDisplayTab" onclick="toggleTab(this); showMetrics();">Real-time metrics</button>
</div>

<div class="center" id='playbackControlTab'>
<div class="center demo-tab" id='playbackControlTab'>
<h4>Playback</h4>
<center>
<p>
Expand Down Expand Up @@ -169,7 +169,7 @@ <h4>Playback</h4>

</div>

<div class="center" id='qualityLevelControlTab'>
<div class="center demo-tab" id='qualityLevelControlTab'>
<h4>Quality-levels</h4>
<center>
<table>
Expand Down Expand Up @@ -209,7 +209,7 @@ <h4>Quality-levels</h4>
</center>
</div>

<div class="center" id='audioTrackControlTab'>
<div class="center demo-tab" id='audioTrackControlTab'>
<h4>Audio-tracks</h4>
<table>
<tr>
Expand All @@ -220,7 +220,7 @@ <h4>Audio-tracks</h4>
</table>
</div>

<div class="center" id='metricsDisplayTab'>
<div class="center demo-tab" id='metricsDisplayTab'>
<h4>Real-time metrics</h4>
<div id="metricsButton">
<button type="button" class="btn btn-xs btn-info" onclick="$('#metricsButtonWindow').toggle();$('#metricsButtonFixed').toggle();windowSliding=!windowSliding; refreshCanvas()">toggle sliding/fixed window</button><br>
Expand Down Expand Up @@ -255,15 +255,15 @@ <h4>Real-time metrics</h4>
</div>
</div>

<div class="center" id='statsDisplayTab'>
<div class="center demo-tab" id='statsDisplayTab'>
<h4>Buffer &amp; Statistics</h4>
<label>Buffer state:</label>
<pre id="bufferedOut"></pre>
<label>General stats:</label>
<pre id='statisticsOut'></pre>
</div>

<div class="center demo-timeline-chart-container" id='timelineTab'>
<div class="center demo-tab demo-timeline-chart-container" id='timelineTab'>
<canvas id="timeline-chart"></canvas>
</div>

Expand Down
17 changes: 7 additions & 10 deletions demo/main.js
Expand Up @@ -12,7 +12,7 @@ const STORAGE_KEYS = {
};

const testStreams = require('../tests/test-streams');
const defaultTestStreamUrl = testStreams['bbb'].url;
const defaultTestStreamUrl = testStreams[Object.keys(testStreams)[0]].url;
const sourceURL = decodeURIComponent(getURLParam('src', defaultTestStreamUrl));

let demoConfig = getURLParam('demoConfig', null);
Expand Down Expand Up @@ -138,8 +138,7 @@ $(document).ready(function () {

video.volume = 0.05;

hideAllTabs();
// $('#timelineTab').show();
toggleTab($('.demo-tab-btn')[0]);

$('#metricsButtonWindow').toggle(window.windowSliding);
$('#metricsButtonFixed').toggle(!window.windowSliding);
Expand Down Expand Up @@ -1413,17 +1412,14 @@ function arrayConcat (inputArray) {
}

function hideAllTabs () {
$('#timelineTab').hide();
$('#playbackControlTab').hide();
$('#qualityLevelControlTab').hide();
$('#audioTrackControlTab').hide();
$('#metricsDisplayTab').hide();
$('#statsDisplayTab').hide();
$('.demo-tab-btn').css('background-color', '');
$('.demo-tab').hide();
}

function toggleTab (tabElId) {
function toggleTab (btn) {
hideAllTabs();
window.hideMetrics();
const tabElId = $(btn).data('tab');
$('#' + tabElId).show();
if (hls) {
if (tabElId === 'timelineTab') {
Expand All @@ -1433,6 +1429,7 @@ function toggleTab (tabElId) {
chart.hide();
}
}
$(btn).css('background-color', 'orange');
}

function appendLog (textElId, message) {
Expand Down
6 changes: 4 additions & 2 deletions src/controller/stream-controller.js
Expand Up @@ -570,7 +570,9 @@ class StreamController extends BaseStreamController {
let media = this.media, previouslyPaused;
if (media) {
previouslyPaused = media.paused;
media.pause();
if (!previouslyPaused) {
media.pause();
}
} else {
// don't restart playback after instant level switch in case media not attached
previouslyPaused = true;
Expand All @@ -596,7 +598,7 @@ class StreamController extends BaseStreamController {
const media = this.media;
if (media && media.buffered.length) {
this.immediateSwitch = false;
if (BufferHelper.isBuffered(media, media.currentTime)) {
if (media.currentTime > 0 && BufferHelper.isBuffered(media, media.currentTime)) {
// only nudge if currentTime is buffered
media.currentTime -= 0.0001;
}
Expand Down
86 changes: 41 additions & 45 deletions tests/functional/auto/setup.js
Expand Up @@ -62,6 +62,7 @@ HttpServer.createServer({
root: './'
}).listen(8000, hostname);

const stringifyResult = (result) => JSON.stringify(result, Object.keys(result).filter(k => k !== 'logs'), 2);
const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
async function retry (attempt, numAttempts = 5, interval = 2000) {
try {
Expand All @@ -77,9 +78,8 @@ async function retry (attempt, numAttempts = 5, interval = 2000) {
}

async function testLoadedData (url, config) {
const result = await browser.executeAsyncScript(
(url, config) => {
let callback = arguments[arguments.length - 1];
const result = await browser.executeAsyncScript(function (url, config) {
const callback = arguments[arguments.length - 1];
window.startStream(url, config, callback);
const video = window.video;
video.onloadeddata = function () {
Expand All @@ -89,12 +89,11 @@ async function testLoadedData (url, config) {
url,
config
);
expect(result, JSON.stringify(result, null, 2)).to.have.property('code').which.equals('loadeddata');
expect(result, stringifyResult(result)).to.have.property('code').which.equals('loadeddata');
}

async function testIdleBufferLength (url, config) {
const result = await browser.executeAsyncScript(
(url, config) => {
const result = await browser.executeAsyncScript(function (url, config) {
const callback = arguments[arguments.length - 1];
const autoplay = false;
window.startStream(url, config, callback, autoplay);
Expand All @@ -105,7 +104,8 @@ async function testIdleBufferLength (url, config) {
if (buffered.length) {
const bufferEnd = buffered.end(buffered.length - 1);
const duration = video.duration;
console.log(`[log] > progress: ${bufferEnd.toFixed(2)}/${duration.toFixed(2)} buffered.length: ${buffered.length}`);
console.log('[log] > progress: ' + bufferEnd.toFixed(2) + '/' + duration.toFixed(2) +
' buffered.length: ' + buffered.length);
if (bufferEnd >= maxBufferLength || bufferEnd > duration - 1) {
callback({ code: 'loadeddata', logs: window.logString });
}
Expand All @@ -115,30 +115,27 @@ async function testIdleBufferLength (url, config) {
url,
config
);
expect(result, JSON.stringify(result, null, 2)).to.have.property('code').which.equals('loadeddata');
expect(result, stringifyResult(result)).to.have.property('code').which.equals('loadeddata');
}

async function testSmoothSwitch (url, config) {
const result = await browser.executeAsyncScript(
(url, config) => {
let callback = arguments[arguments.length - 1];
const result = await browser.executeAsyncScript(function (url, config) {
const callback = arguments[arguments.length - 1];
window.startStream(url, config, callback);
const video = window.video;
window.hls.once(window.Hls.Events.FRAG_CHANGED, (event, data) => {
window.switchToHighestLevel('next');
});
window.hls.on(window.Hls.Events.LEVEL_SWITCHED, (event, data) => {
console.log(`[test] > level switched: ${data.level}`);
console.log('[test] > level switched: ' + data.level);
let currentTime = video.currentTime;
if (data.level === window.hls.levels.length - 1) {
console.log(`[test] > switched on level: ${data.level}`);
console.log('[test] > switched on level: ' + data.level);
window.setTimeout(function () {
let newCurrentTime = video.currentTime;
console.log(
`[test] > currentTime delta : ${newCurrentTime - currentTime}`
);
console.log('[test] > currentTime delta : ' + (newCurrentTime - currentTime));
callback({
code: newCurrentTime > currentTime,
currentTimeDelta: newCurrentTime - currentTime,
logs: window.logString
});
}, 2000);
Expand All @@ -148,13 +145,12 @@ async function testSmoothSwitch (url, config) {
url,
config
);
expect(result, JSON.stringify(result, null, 2)).to.have.property('code').which.equals(true);
expect(result, stringifyResult(result)).to.have.property('currentTimeDelta').which.is.gt(0);
}

async function testSeekOnLive (url, config) {
const result = await browser.executeAsyncScript(
(url, config) => {
let callback = arguments[arguments.length - 1];
const result = await browser.executeAsyncScript(function (url, config) {
const callback = arguments[arguments.length - 1];
window.startStream(url, config, callback);
const video = window.video;
video.onloadeddata = function () {
Expand All @@ -169,18 +165,23 @@ async function testSeekOnLive (url, config) {
url,
config
);
expect(result, JSON.stringify(result, null, 2)).to.have.property('code').which.equals('seeked');
expect(result, stringifyResult(result)).to.have.property('code').which.equals('seeked');
}

async function testSeekOnVOD (url, config) {
const result = await browser.executeAsyncScript(
(url, config) => {
let callback = arguments[arguments.length - 1];
const result = await browser.executeAsyncScript(function (url, config) {
const callback = arguments[arguments.length - 1];
window.startStream(url, config, callback);
const video = window.video;
video.onloadeddata = function () {
window.setTimeout(function () {
video.currentTime = video.duration - 5;
// Fail test early if more than 2 buffered ranges are found
video.onprogress = function () {
if (video.buffered.length > 2) {
callback({ code: 'buffer-gaps', bufferedRanges: video.buffered.length, logs: window.logString });
}
};
}, 5000);
};
video.onended = function () {
Expand All @@ -190,13 +191,12 @@ async function testSeekOnVOD (url, config) {
url,
config
);
expect(result, JSON.stringify(result, null, 2)).to.have.property('code').which.equals('ended');
expect(result, stringifyResult(result)).to.have.property('code').which.equals('ended');
}

async function testSeekEndVOD (url, config) {
const result = await browser.executeAsyncScript(
(url, config) => {
let callback = arguments[arguments.length - 1];
const result = await browser.executeAsyncScript(function (url, config) {
const callback = arguments[arguments.length - 1];
window.startStream(url, config, callback);
const video = window.video;
video.onloadeddata = function () {
Expand All @@ -211,13 +211,12 @@ async function testSeekEndVOD (url, config) {
url,
config
);
expect(result, JSON.stringify(result, null, 2)).to.have.property('code').which.equals('ended');
expect(result, stringifyResult(result)).to.have.property('code').which.equals('ended');
}

async function testIsPlayingVOD (url, config) {
const result = await browser.executeAsyncScript(
(url, config) => {
let callback = arguments[arguments.length - 1];
const result = await browser.executeAsyncScript(function (url, config) {
const callback = arguments[arguments.length - 1];
window.startStream(url, config, callback);
const video = window.video;
video.onloadeddata = function () {
Expand All @@ -229,29 +228,26 @@ async function testIsPlayingVOD (url, config) {
let currentTime = video.currentTime;
if (expectedPlaying) {
window.setTimeout(function () {
console.log(
`[test] > video expected playing. [last currentTime/new currentTime]=[${currentTime}/${video.currentTime}]`
);
console.log('[test] > video expected playing. last currentTime/new currentTime=' +
currentTime + '/' + video.currentTime);
callback({ playing: currentTime !== video.currentTime });
}, 5000);
} else {
console.log(
`[test] > video not playing. [paused/ended/buffered.length]=[${video.paused}/${video.ended}/${video.buffered.length}]`
);
console.log('[test] > video not playing. paused/ended/buffered.length=' +
video.paused + '/' + video.ended + '/' + video.buffered.length);
callback({ playing: false });
}
};
},
url,
config
);
expect(result, JSON.stringify(result, null, 2)).to.have.property('playing').which.is.true;
expect(result, stringifyResult(result)).to.have.property('playing').which.is.true;
}

async function testSeekBackToStart (url, config) {
const result = await browser.executeAsyncScript(
(url, config) => {
let callback = arguments[arguments.length - 1];
const result = await browser.executeAsyncScript(function (url, config) {
const callback = arguments[arguments.length - 1];
window.startStream(url, config, callback);
const video = window.video;
video.ontimeupdate = function () {
Expand All @@ -275,7 +271,7 @@ async function testSeekBackToStart (url, config) {
url,
config
);
expect(result, JSON.stringify(result, null, 2)).to.have.property('playing').which.is.true;
expect(result, stringifyResult(result)).to.have.property('playing').which.is.true;
}

describe(`testing hls.js playback in the browser on "${browserDescription}"`, function () {
Expand Down Expand Up @@ -421,7 +417,7 @@ describe(`testing hls.js playback in the browser on "${browserDescription}"`, fu
testIsPlayingVOD.bind(null, url, config)
);
it(
`should seek 5s from end and receive video ended event for ${stream.description}`,
`should seek 5s from end and receive video ended event for ${stream.description} with 2 or less buffered ranges`,
testSeekOnVOD.bind(null, url, config)
);
// it(`should seek on end and receive video ended event for ${stream.description}`, testSeekEndVOD.bind(null, url));
Expand Down
2 changes: 1 addition & 1 deletion tests/functional/auto/testbench.js
Expand Up @@ -92,7 +92,7 @@ function startStream (streamUrl, config, callback, autoplay) {
var playPromise = video.play();
if (playPromise) {
playPromise.catch(function (error) {
console.log('[test] > video.play() failed with error:', error);
console.log('[test] > video.play() failed with error: ' + error.name + ' ' + error.message);
if (error.name === 'NotAllowedError') {
console.log('[test] > Attempting to play with video muted');
video.muted = true;
Expand Down

0 comments on commit f98348d

Please sign in to comment.