Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/tr 1252 handle stalled event #305

Merged
merged 14 commits into from Jun 4, 2021
114 changes: 102 additions & 12 deletions src/mediaplayer.js
Expand Up @@ -13,7 +13,7 @@
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* Copyright (c) 2015 (original work) Open Assessment Technologies SA ;
* Copyright (c) 2015-2021 (original work) Open Assessment Technologies SA ;
*/
/**
* @author Jean-Sébastien Conan <jean-sebastien.conan@vesperiagroup.com>
Expand Down Expand Up @@ -778,9 +778,22 @@ const _nativePlayer = function (mediaplayer) {
mediaplayer._onEnd();
})
.on(`timeupdate${_ns}`, function () {
if (mediaplayer.stalledTimer) {
if (mediaplayer.stalledTimeUpdateCount === 5) {
clearTimeout(mediaplayer.stalledTimer);
mediaplayer.stalledTimer = null;
}
else {
mediaplayer.stalledTimeUpdateCount++;
}
}
mediaplayer._onTimeUpdate();
})
.on('loadstart', function () {
if (mediaplayer.is('stalled')) {
return;
}

if (media.networkState === HTMLMediaElement.NETWORK_NO_SOURCE) {
mediaplayer._onError();
}
Expand All @@ -805,6 +818,26 @@ const _nativePlayer = function (mediaplayer) {
mediaplayer._onRecoverError();
}
mediaplayer._onReady();

// seek back to the previous position after recover from stalled
if (mediaplayer.is('stalled')) {
mediaplayer.play(mediaplayer.positionBeforeStalled);
}
})
.on(`stalled${_ns}`, () => {
mediaplayer.stalledTimeUpdateCount = 0;
mediaplayer.stalledTimer = setTimeout(() => {
const position = mediaplayer.getPosition();
if (position) {
mediaplayer.positionBeforeStalled = position;
}
mediaplayer._setState('stalled', true);
mediaplayer._setState('ready', false);
}, 2000);
})
.on(`playing${_ns}`, () => {
mediaplayer._setState('stalled', false);
mediaplayer._setState('ready', true);
});

if (_debugMode) {
Expand Down Expand Up @@ -833,7 +866,9 @@ const _nativePlayer = function (mediaplayer) {
'suspend',
'timeupdate',
'volumechange',
'waiting'
'waiting',
'stalled',
'playing'
],
function (ev) {
$media.on(ev + _ns, function (e) {
Expand Down Expand Up @@ -1092,6 +1127,11 @@ const mediaplayer = {
this.$container = $(renderTo).append(this.$component);
}

// add class if it is stalled
if (this.is('stalled')) {
this._setState('stalled', true);
}

/**
* Triggers a render event
* @event mediaplayer#render
Expand All @@ -1102,6 +1142,48 @@ const mediaplayer = {
return this;
},

/**
* Reloads media player after it was stalled
*/
reload() {
/**
* Triggers a reload event
* @event mediaplayer#reload
*/
this.trigger('reload');

// destroy player
if (this.player) {
this.player.destroy();
}

// remove events and component
if (this.$component) {
this._unbindEvents();
this._destroySlider(this.$seekSlider);
this._destroySlider(this.$volumeSlider);

this.$component.remove();
this.$component = null;
}

// rerender
this.render();
},

/**
* Set initial states
*/
setInitialStates() {
if (!this.is('stalled')) {
this._setState('ready', true);
}
this._setState('canplay', true);
this._setState('canpause', this.config.canPause);
this._setState('canseek', this.config.canSeek);
this._setState('loading', false);
},

/**
* Sets the start position inside the media
* @param {Number} time - The start position in seconds
Expand Down Expand Up @@ -1751,11 +1833,23 @@ const mediaplayer = {
}
});

this.$player.on(`click${_ns}`, () => {
if (this.is('playing')) {
this.pause();
this.$player.on(`click${_ns}`, event => {
const $target = $(event.target);
const $action = $target.closest('.action');

// if action was clicked
if ($action.length) {
const id = $action.data('control');
if (_.isFunction(this[id])) {
this[id]();
}
// default action is toggle play
} else {
this.play();
if (this.is('playing')) {
this.pause();
} else {
this.play();
}
}
});

Expand Down Expand Up @@ -1931,11 +2025,7 @@ const mediaplayer = {
*/
_onReady: function _onReady() {
this._updateDuration(this.player.getDuration());
this._setState('ready', true);
this._setState('canplay', true);
this._setState('canpause', this.config.canPause);
this._setState('canseek', this.config.canSeek);
this._setState('loading', false);
this.setInitialStates();

/**
* Triggers a media ready event
Expand Down Expand Up @@ -2151,7 +2241,7 @@ const mediaplayer = {
* @private
*/
_canPlay: function _canPlay() {
return this.is('ready') && !this.is('disabled') && !this.is('hidden') && !this._playLimitReached();
return (this.is('ready') || this.is('stalled')) && !this.is('disabled') && !this.is('hidden') && !this._playLimitReached();
},

/**
Expand Down
44 changes: 39 additions & 5 deletions src/mediaplayer/scss/player.scss
Expand Up @@ -78,10 +78,8 @@ $controlsHeight: 36px;
z-index: 2;
top: 50%;
left: 50%;
width: 64px;
height: 64px;
transform: translate(-50%);
margin-top: -32px;
margin-left: -32px;
text-align: center;
text-decoration: none;
display: none;
Expand All @@ -98,6 +96,30 @@ $controlsHeight: 36px;
opacity: 0.6;
}
}

&.reload {
width: 100%;
font-size: 50px;
line-height: 30px;
Comment on lines +102 to +103
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: no other way that setting it in pixels? How will it present on devices with high DPI or devices with huge resolution?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually, it should not be a problem we have conversion for this case sure it could be rem / em

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I checked how the icon was implemented, and it was pixels, so I used pixels to overwrite them.


&:hover {
.icon {
opacity: 1;
}
}

.icon {
opacity: 0.6;
background: none;
}
.message {
font-size: 20px;
}

.icon, .message {
text-shadow: 1px 0 0 #000, 0 -1px 0 #000, 0 1px 0 #000, -1px 0 0 #000;
}
}
}
}

Expand Down Expand Up @@ -405,7 +427,7 @@ $controlsHeight: 36px;
}
}

&.error {
&.error:not(.stalled) {
.media, .controls {
display: none;
}
Expand All @@ -424,7 +446,7 @@ $controlsHeight: 36px;
}
}

&.loading::before {
&.loading:not(.stalled)::before {
@keyframes spinner {
to { transform: rotate(360deg); }
}
Expand All @@ -443,4 +465,16 @@ $controlsHeight: 36px;
border-top-color: #07d;
animation: spinner .6s linear infinite;
}

&.stalled {
.video {
filter: blur(4px);
opacity: 0.4;
}
.overlay {
[data-control="reload"] {
display: inline-block;
}
}
}
}
5 changes: 5 additions & 0 deletions src/mediaplayer/tpl/player.tpl
Expand Up @@ -32,6 +32,11 @@
<div class="overlay">
<a class="action play" data-control="play"><span class="icon icon-play" title="{{__ 'Play'}}"></span></a>
<a class="action play" data-control="pause"><span class="icon icon-pause" title="{{__ 'Pause'}}"></span></a>
<a class="action reload" data-control="reload">
<div class="icon icon-reload" title="{{__ 'Reload'}}"></div>
<div class="message">{{__ 'You are encountering a prolonged connectivity loss.'}}</div>
<div class="message">{{__ 'Click to reload.'}}</div>
</a>
</div>
</div>
<div class="controls">
Expand Down
1 change: 1 addition & 0 deletions test/mediaplayer/test.html
Expand Up @@ -23,6 +23,7 @@
<div id="fixture-4"></div>
<div id="fixture-5"></div>
<div id="fixture-6"></div>
<div id="fixture-7"></div>
</div>
</body>
</html>
44 changes: 43 additions & 1 deletion test/mediaplayer/test.js
Expand Up @@ -13,7 +13,7 @@
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* Copyright (c) 2015 (original work) Open Assessment Technologies SA ;
* Copyright (c) 2015-2021 (original work) Open Assessment Technologies SA ;
*/
/**
* @author Jean-Sébastien Conan <jean-sebastien.conan@vesperiagroup.com>
Expand Down Expand Up @@ -1039,4 +1039,46 @@ define(['jquery', 'lodash', 'ui/mediaplayer'], function($, _, mediaplayer) {
player.destroy();
});

QUnit.only('stalled', function(assert) {
btamas marked this conversation as resolved.
Show resolved Hide resolved
const done = assert.async();

assert.expect(5);

mediaplayer({
url: '/test/mediaplayer/samples/video.mp4',
type: 'video',
renderTo: '#fixture-7'
})
.on('render', function($dom) {
// set state that should be kept
this.timesPlayed = 1;

$dom.find('video').trigger('stalled');

setTimeout(() => {
assert.equal(this.is('stalled'), true, 'player is stalled after 2 seconds');

//simulate user click to reload button
this.reload();
}, 2000);
})
.on('reload', function() {
assert.ok(true, 'reload event is fired');

// add listener for rerender
this.on('render', function($dom) {
assert.equal(this.getTimesPlayed(), 1, 'timesPlayed is kept');
assert.equal(this.is('stalled'), true, 'player still stalled after reload');

// simulate video start playing
$dom.find('video').trigger('playing');
assert.equal(this.is('stalled'), false);

this.destroy();
});
})
.on('destroy', function() {
done();
});
});
});