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

issue/14-b Added playsinline #17

Merged
merged 4 commits into from
Nov 1, 2021
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions example.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,16 @@
"_classes": "",
"_layout": "full",
"_component": "youtube",
"title": "Karle pyar karle",
"displayTitle": "Karle pyar karle",
"body": "The legendary Asha Bosle…",
"title": "The first video on YouTube",
"displayTitle": "The first video on YouTube",
"body": "Entitled 'Me at the zoo', this was the first video ever uploaded to YouTube, by platform co-founder <a href='https://en.wikipedia.org/wiki/Jawed_Karim' target='_blank'>Jawed Karim</a>",
"instruction": "",
"_setCompletionOn": "play",
"_media": {
"_source": "//www.youtube.com/embed/mDa42EkgO1A",
"_source": "//www.youtube.com/embed/jNQXAC9IVRw",
"_controls": true,
"_allowFullscreen": true,
"_playsinline": false,
"_aspectRatio": 1.33,
"_autoplay": false,
"_showRelated": false,
Expand Down
176 changes: 176 additions & 0 deletions js/YouTubeView.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
import Adapt from 'core/js/adapt';
import ComponentView from 'core/js/views/componentView';

export default class YouTubeView extends ComponentView {

get template() {
return 'youtube';
}

events() {
return {
'click .js-youtube-inline-transcript-toggle': 'onToggleInlineTranscript',
'click .js-youtube-external-transcript-click': 'onExternalTranscriptClicked',
'click .js-skip-to-transcript': 'onSkipToTranscript'
};
}

initialize() {
super.initialize();
_.bindAll(this, 'onPlayerStateChange', 'onPlayerReady', 'onInview');
this.player = null;
this.debouncedTriggerGlobalEvent = _.debounce(this.triggerGlobalEvent.bind(this), 1000);
if (window.onYouTubeIframeAPIReady !== undefined) return;
window.onYouTubeIframeAPIReady = () => {
Adapt.log.info('YouTube iframe API loaded');
Adapt.youTubeIframeAPIReady = true;
Adapt.trigger('youTubeIframeAPIReady');
};
$.getScript('//www.youtube.com/iframe_api');
}

preRender() {
this.listenTo(Adapt, {
'device:resize device:changed': this.setIFrameSize,
'media:stop': this.onMediaStop
});
}

setIFrameSize() {
const $iframe = this.$('iframe');
const widgetWidth = this.$('.component__widget').width();
$iframe.width(widgetWidth);
// default aspect ratio to 16:9 if not specified
const aspectRatio = parseFloat(this.model.get('_media')._aspectRatio) || 1.778;
oliverfoster marked this conversation as resolved.
Show resolved Hide resolved
if (isNaN(aspectRatio)) return;
$iframe.height(widgetWidth / aspectRatio);
}

postRender() {
// for HTML/HBS parameters: https://developers.google.com/youtube/player_parameters
if (!this.model.get('_media')?._source) {
this.setReadyStatus();
this.model.setCompletionStatus();
return;
}
if (Adapt.youTubeIframeAPIReady === true) {
this.onYouTubeIframeAPIReady();
return;
}
this.listenToOnce(Adapt, 'youTubeIframeAPIReady', this.onYouTubeIframeAPIReady);
}

remove() {
if (this.player !== null) {
this.player.destroy();
}
super.remove();
}

setupEventListeners() {
this.completionEvent = (this.model.get('_setCompletionOn') || 'play');
if (this.completionEvent !== 'inview') return;
this.setupInviewCompletion('.component__widget');
}

onYouTubeIframeAPIReady() {
this.player = new window.YT.Player(this.$('iframe').get(0), {
events: {
onStateChange: this.onPlayerStateChange,
onReady: this.onPlayerReady
}
});
this.isPlaying = false;
this.setReadyStatus();
this.setupEventListeners();
this.setIFrameSize();
}

onMediaStop(view) {
// if it was this view that triggered the media:stop event, ignore it
if (view && view.cid === this.cid) return;
if (!this.isPlaying) return;
this.player.pauseVideo();
}

onPlayerReady() {
if (!this.model.get('_media')._playbackQuality) return;
this.player.setPlaybackQuality(this.model.get('_media')._playbackQuality);
}

/**
* this seems to have issues in Chrome if the user is logged into YouTube (possibly any Google account) - the API just doesn't broadcast the events
* but instead throws the error:
* Failed to execute 'postMessage' on 'DOMWindow': The target origin provided ('https://www.youtube.com') does not match the recipient window's origin ('http://www.youtube.com').
* This is documented here:
* https://code.google.com/p/gdata-issues/issues/detail?id=5788
* but I haven't managed to get any of the workarounds to work... :-(
*/
onPlayerStateChange(e) {
switch (e.data) {
case window.YT.PlayerState.PLAYING:
Adapt.trigger('media:stop', this);
this.debouncedTriggerGlobalEvent('play');// use debounced version because seeking whilst playing will trigger two 'play' events
this.isPlaying = true;
if (this.model.get('_setCompletionOn') === 'play') {
this.setCompletionStatus();
}
break;
case window.YT.PlayerState.PAUSED:
this.isPlaying = false;
this.triggerGlobalEvent('pause');
break;
case window.YT.PlayerState.ENDED:
this.triggerGlobalEvent('ended');
if (this.model.get('_setCompletionOn') === 'ended') {
this.setCompletionStatus();
}
break;
}
}

onSkipToTranscript() {
// need slight delay before focussing button to make it work when JAWS is running
// see https://github.com/adaptlearning/adapt_framework/issues/2427
_.delay(() => {
Adapt.a11y.focusFirst(this.$('.youtube__transcript-btn'), { defer: true });
}, 250);
}

onToggleInlineTranscript(e) {
if (e && e.preventDefault) e.preventDefault();

const $transcriptBodyContainer = this.$('.youtube__transcript-body-inline');
const $button = this.$('.youtube__transcript-btn-inline');
const $buttonText = $button.find('.youtube__transcript-btn-text');
const config = this.model.get('_transcript');
const shouldOpen = !$transcriptBodyContainer.hasClass('inline-transcript-open');
const buttonText = shouldOpen ?
config.inlineTranscriptCloseButton :
config.inlineTranscriptButton;

$transcriptBodyContainer
.stop(true).slideToggle(() => $(window).resize())
.toggleClass('inline-transcript-open', shouldOpen);
$button.attr('aria-expanded', shouldOpen);
$buttonText.html(buttonText);

if (!shouldOpen || config._setCompletionOnView === false) return;
this.setCompletionStatus();
}

onExternalTranscriptClicked() {
if (this.model.get('_transcript')._setCompletionOnView === false) return;
this.setCompletionStatus();
}

triggerGlobalEvent(eventType) {
Adapt.trigger('media', {
isVideo: true,
type: eventType,
src: this.model.get('_media')._source,
platform: 'YouTube'
});
}

}
188 changes: 1 addition & 187 deletions js/adapt-youtube.js
Original file line number Diff line number Diff line change
@@ -1,192 +1,6 @@
import Adapt from 'core/js/adapt';
import ComponentView from 'core/js/views/componentView';
import ComponentModel from 'core/js/models/componentModel';

class YouTubeView extends ComponentView {

get template() {
return 'youtube';
}

events() {
return {
'click .js-youtube-inline-transcript-toggle': 'onToggleInlineTranscript',
'click .js-youtube-external-transcript-click': 'onExternalTranscriptClicked',
'click .js-skip-to-transcript': 'onSkipToTranscript'
};
}

initialize() {
super.initialize();

_.bindAll(this, 'onPlayerStateChange', 'onPlayerReady', 'onInview');

this.player = null;
this.debouncedTriggerGlobalEvent = _.debounce(this.triggerGlobalEvent.bind(this), 1000);

if (window.onYouTubeIframeAPIReady !== undefined) return;
window.onYouTubeIframeAPIReady = () => {
Adapt.log.info('YouTube iframe API loaded');
Adapt.youTubeIframeAPIReady = true;
Adapt.trigger('youTubeIframeAPIReady');
};
$.getScript('//www.youtube.com/iframe_api');
}

preRender() {
this.listenTo(Adapt, {
'device:resize device:changed': this.setIFrameSize,
'media:stop': this.onMediaStop
});
}

setIFrameSize() {
const $iframe = this.$('iframe');
const widgetWidth = this.$('.component__widget').width();

$iframe.width(widgetWidth);

// default aspect ratio to 16:9 if not specified
const aspectRatio = parseFloat(this.model.get('_media')._aspectRatio) || 1.778;
if (isNaN(aspectRatio)) return;
$iframe.height(widgetWidth / aspectRatio);
}

postRender() {
// for HTML/HBS parameters: https://developers.google.com/youtube/player_parameters
if (Adapt.youTubeIframeAPIReady === true) {
this.onYouTubeIframeAPIReady();
return;
}
Adapt.once('youTubeIframeAPIReady', this.onYouTubeIframeAPIReady, this);
}

remove() {
if (this.player !== null) {
this.player.destroy();
}

super.remove();
}

setupEventListeners() {
this.completionEvent = (this.model.get('_setCompletionOn') || 'play');

if (this.completionEvent !== 'inview') return;
this.setupInviewCompletion('.component__widget');
}

onYouTubeIframeAPIReady() {
this.player = new window.YT.Player(this.$('iframe').get(0), {
events: {
onStateChange: this.onPlayerStateChange,
onReady: this.onPlayerReady
}
});

this.isPlaying = false;

this.setReadyStatus();

this.setupEventListeners();

this.setIFrameSize();
}

onMediaStop(view) {
// if it was this view that triggered the media:stop event, ignore it
if (view && view.cid === this.cid) return;

if (!this.isPlaying) return;
this.player.pauseVideo();
}

onPlayerReady() {
if (!this.model.get('_media')._playbackQuality) return;
this.player.setPlaybackQuality(this.model.get('_media')._playbackQuality);
}

/**
* this seems to have issues in Chrome if the user is logged into YouTube (possibly any Google account) - the API just doesn't broadcast the events
* but instead throws the error:
* Failed to execute 'postMessage' on 'DOMWindow': The target origin provided ('https://www.youtube.com') does not match the recipient window's origin ('http://www.youtube.com').
* This is documented here:
* https://code.google.com/p/gdata-issues/issues/detail?id=5788
* but I haven't managed to get any of the workarounds to work... :-(
*/
onPlayerStateChange(e) {
switch (e.data) {
case window.YT.PlayerState.PLAYING:
Adapt.trigger('media:stop', this);

this.debouncedTriggerGlobalEvent('play');// use debounced version because seeking whilst playing will trigger two 'play' events

this.isPlaying = true;

if (this.model.get('_setCompletionOn') === 'play') {
this.setCompletionStatus();
}
break;
case window.YT.PlayerState.PAUSED:
this.isPlaying = false;

this.triggerGlobalEvent('pause');
break;
case window.YT.PlayerState.ENDED:
this.triggerGlobalEvent('ended');

if (this.model.get('_setCompletionOn') === 'ended') {
this.setCompletionStatus();
}
break;
}
}

onSkipToTranscript() {
// need slight delay before focussing button to make it work when JAWS is running
// see https://github.com/adaptlearning/adapt_framework/issues/2427
_.delay(() => {
Adapt.a11y.focusFirst(this.$('.youtube__transcript-btn'), { defer: true });
}, 250);
}

onToggleInlineTranscript(e) {
if (e && e.preventDefault) e.preventDefault();

const $transcriptBodyContainer = this.$('.youtube__transcript-body-inline');
const $button = this.$('.youtube__transcript-btn-inline');
const $buttonText = $button.find('.youtube__transcript-btn-text');
const config = this.model.get('_transcript');
const shouldOpen = !$transcriptBodyContainer.hasClass('inline-transcript-open');
const buttonText = shouldOpen ?
config.inlineTranscriptCloseButton :
config.inlineTranscriptButton;

$transcriptBodyContainer
.stop(true).slideToggle(() => $(window).resize())
.toggleClass('inline-transcript-open', shouldOpen);
$button.attr('aria-expanded', shouldOpen);
$buttonText.html(buttonText);

if (!shouldOpen || config._setCompletionOnView === false) return;
this.setCompletionStatus();
}

onExternalTranscriptClicked() {
if (this.model.get('_transcript')._setCompletionOnView === false) return;
this.setCompletionStatus();
}

triggerGlobalEvent(eventType) {
Adapt.trigger('media', {
isVideo: true,
type: eventType,
src: this.model.get('_media')._source,
platform: 'YouTube'
});
}

}
import YouTubeView from './YouTubeView';

export default Adapt.register('youtube', {
model: ComponentModel.extend({}),
Expand Down
Loading