UNPKG

@vime/core

Version:

Customizable, extensible, accessible and framework agnostic media player.

379 lines (372 loc) 13.7 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); const index = require('./index-86498cbd.js'); const network = require('./network-7dc3feca.js'); const withComponentRegistry = require('./withComponentRegistry-90ec334c.js'); const MediaType = require('./MediaType-8f0adf5d.js'); const ViewType = require('./ViewType-ea1402c0.js'); const ProviderConnect = require('./ProviderConnect-100da60f.js'); const withProviderContext = require('./withProviderContext-16bcb095.js'); require('./support-e1714cb5.js'); require('./Provider-b6123cae.js'); require('./PlayerProps-4bbfc16a.js'); require('./withPlayerContext-77ea833f.js'); require('./PlayerEvents-79156eee.js'); const mapYouTubePlaybackQuality = (quality) => { switch (quality) { case "unknown" /* Unknown */: return undefined; case "tiny" /* Tiny */: return '144p'; case "small" /* Small */: return '240p'; case "medium" /* Medium */: return '360p'; case "large" /* Large */: return '480p'; case "hd720" /* Hd720 */: return '720p'; case "hd1080" /* Hd1080 */: return '1080p'; case "highres" /* Highres */: return '1440p'; case "max" /* Max */: return '2160p'; default: return undefined; } }; const youtubeCss = ":host{z-index:var(--vm-media-z-index)}"; var __awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; const posterCache = new Map(); const YouTube = class { constructor(hostRef) { index.registerInstance(this, hostRef); this.vmLoadStart = index.createEvent(this, "vmLoadStart", 7); this.defaultInternalState = {}; this.internalState = { paused: true, duration: 0, seeking: false, playbackReady: false, playbackStarted: false, currentTime: 0, lastTimeUpdate: 0, playbackRate: 1, state: -1, }; this.embedSrc = ''; this.mediaTitle = ''; /** * Whether cookies should be enabled on the embed. */ this.cookies = false; /** * Whether the fullscreen control should be shown. */ this.showFullscreenControl = true; /** @internal */ this.language = 'en'; /** @internal */ this.autoplay = false; /** @internal */ this.controls = false; /** @internal */ this.loop = false; /** @internal */ this.muted = false; /** @internal */ this.playsinline = false; withComponentRegistry.withComponentRegistry(this); ProviderConnect.withProviderConnect(this); withProviderContext.withProviderContext(this); } onVideoIdChange() { if (!this.videoId) { this.embedSrc = ''; return; } this.embedSrc = `${this.getOrigin()}/embed/${this.videoId}`; this.fetchPosterURL = this.findPosterURL(); } onCustomPosterChange() { this.dispatch('currentPoster', this.poster); } connectedCallback() { this.dispatch = ProviderConnect.createProviderDispatcher(this); this.dispatch('viewType', ViewType.ViewType.Video); this.onVideoIdChange(); this.defaultInternalState = Object.assign({}, this.internalState); } componentWillLoad() { this.initialMuted = this.muted; } /** @internal */ getAdapter() { return __awaiter(this, void 0, void 0, function* () { const canPlayRegex = /(?:youtu\.be|youtube|youtube\.com|youtube-nocookie\.com)\/(?:embed\/|v\/|watch\?v=|watch\?.+&v=|)((?:\w|-){11})/; return { getInternalPlayer: () => __awaiter(this, void 0, void 0, function* () { return this.embed; }), play: () => __awaiter(this, void 0, void 0, function* () { this.remoteControl("playVideo" /* Play */); }), pause: () => __awaiter(this, void 0, void 0, function* () { this.remoteControl("pauseVideo" /* Pause */); }), canPlay: (type) => __awaiter(this, void 0, void 0, function* () { return withComponentRegistry.isString(type) && canPlayRegex.test(type); }), setCurrentTime: (time) => __awaiter(this, void 0, void 0, function* () { if (time !== this.internalState.currentTime) { this.remoteControl("seekTo" /* Seek */, time); } }), setMuted: (muted) => __awaiter(this, void 0, void 0, function* () { muted ? this.remoteControl("mute" /* Mute */) : this.remoteControl("unMute" /* Unmute */); }), setVolume: (volume) => __awaiter(this, void 0, void 0, function* () { this.remoteControl("setVolume" /* SetVolume */, volume); }), canSetPlaybackRate: () => __awaiter(this, void 0, void 0, function* () { return true; }), setPlaybackRate: (rate) => __awaiter(this, void 0, void 0, function* () { this.remoteControl("setPlaybackRate" /* SetPlaybackRate */, rate); }), }; }); } getOrigin() { return !this.cookies ? 'https://www.youtube-nocookie.com' : 'https://www.youtube.com'; } getPreconnections() { return [ this.getOrigin(), 'https://www.google.com', 'https://googleads.g.doubleclick.net', 'https://static.doubleclick.net', 'https://s.ytimg.com', 'https://i.ytimg.com', ]; } remoteControl(command, arg) { return this.embed.postMessage({ event: 'command', func: command, args: arg ? [arg] : undefined, }); } buildParams() { return { enablejsapi: 1, cc_lang_pref: this.language, hl: this.language, fs: this.showFullscreenControl ? 1 : 0, controls: this.controls ? 1 : 0, disablekb: !this.controls ? 1 : 0, iv_load_policy: this.controls ? 1 : 3, mute: this.initialMuted ? 1 : 0, playsinline: this.playsinline ? 1 : 0, autoplay: this.autoplay ? 1 : 0, }; } onEmbedSrcChange() { this.vmLoadStart.emit(); this.dispatch('viewType', ViewType.ViewType.Video); } onEmbedLoaded() { // Seems like we have to wait a random small delay or else YT player isn't ready. window.setTimeout(() => this.embed.postMessage({ event: 'listening' }), 100); } findPosterURL() { return __awaiter(this, void 0, void 0, function* () { if (posterCache.has(this.videoId)) return posterCache.get(this.videoId); const posterURL = (quality) => `https://i.ytimg.com/vi/${this.videoId}/${quality}.jpg`; /** * We are testing a that the image has a min-width of 121px because if the thumbnail does not * exist YouTube returns a blank/error image that is 120px wide. */ return network.loadImage(posterURL('maxresdefault'), 121) // 1080p (no padding) .catch(() => network.loadImage(posterURL('sddefault'), 121)) // 640p (padded 4:3) .catch(() => network.loadImage(posterURL('hqdefault'), 121)) // 480p (padded 4:3) .then(img => { const poster = img.src; posterCache.set(this.videoId, poster); return poster; }); }); } onCued() { if (this.internalState.playbackReady) return; this.internalState = Object.assign({}, this.defaultInternalState); this.dispatch('currentSrc', this.embedSrc); this.dispatch('mediaType', MediaType.MediaType.Video); this.fetchPosterURL.then(poster => { var _a; this.dispatch('currentPoster', (_a = this.poster) !== null && _a !== void 0 ? _a : poster); this.dispatch('playbackReady', true); }); this.internalState.playbackReady = true; } onPlayerStateChange(state) { // Sometimes the embed falls back to an unstarted state for some unknown reason, this will // make sure the player is configured to the right starting state. if (this.internalState.playbackReady && state === -1 /* Unstarted */) { this.internalState.paused = true; this.internalState.playbackStarted = false; this.dispatch('buffering', false); this.dispatch('paused', true); this.dispatch('playbackStarted', false); return; } const isPlaying = state === 1 /* Playing */; const isBuffering = state === 3 /* Buffering */; this.dispatch('buffering', isBuffering); // Attempt to detect `play` events early. if (this.internalState.paused && (isBuffering || isPlaying)) { this.internalState.paused = false; this.dispatch('paused', false); if (!this.internalState.playbackStarted) { this.dispatch('playbackStarted', true); this.internalState.playbackStarted = true; } } switch (state) { case 5 /* Cued */: this.onCued(); break; case 1 /* Playing */: // Incase of autoplay which might skip `Cued` event. this.onCued(); this.dispatch('playing', true); break; case 2 /* Paused */: this.internalState.paused = true; this.dispatch('paused', true); break; case 0 /* Ended */: if (this.loop) { window.setTimeout(() => { this.remoteControl("playVideo" /* Play */); }, 150); } else { this.dispatch('playbackEnded', true); this.internalState.paused = true; this.dispatch('paused', true); } break; } this.internalState.state = state; } calcCurrentTime(time) { let currentTime = time; if (this.internalState.state === 0 /* Ended */) { return this.internalState.duration; } if (this.internalState.state === 1 /* Playing */) { const elapsedTime = (Date.now() / 1e3 - this.defaultInternalState.lastTimeUpdate) * this.internalState.playbackRate; if (elapsedTime > 0) currentTime += Math.min(elapsedTime, 1); } return currentTime; } onTimeChange(time) { const currentTime = this.calcCurrentTime(time); this.dispatch('currentTime', currentTime); // This is the only way to detect `seeking`. if (Math.abs(this.internalState.currentTime - currentTime) > 1.5) { this.internalState.seeking = true; this.dispatch('seeking', true); } this.internalState.currentTime = currentTime; } onBufferedChange(buffered) { this.dispatch('buffered', buffered); /** * This is the only way to detect `seeked`. Unfortunately while the player is `paused` `seeking` * and `seeked` will fire at the same time, there are no updates inbetween -_-. We need an * artifical delay between the two events. */ if (this.internalState.seeking && buffered > this.internalState.currentTime) { window.setTimeout(() => { this.internalState.seeking = false; this.dispatch('seeking', false); }, this.internalState.paused ? 100 : 0); } } onEmbedMessage(event) { const message = event.detail; const { info } = message; if (!info) return; if (withComponentRegistry.isObject(info.videoData)) this.dispatch('mediaTitle', info.videoData.title); if (withComponentRegistry.isNumber(info.duration)) { this.internalState.duration = info.duration; this.dispatch('duration', info.duration); } if (withComponentRegistry.isArray(info.availablePlaybackRates)) { this.dispatch('playbackRates', info.availablePlaybackRates); } if (withComponentRegistry.isNumber(info.playbackRate)) { this.internalState.playbackRate = info.playbackRate; this.dispatch('playbackRate', info.playbackRate); } if (withComponentRegistry.isNumber(info.currentTime)) this.onTimeChange(info.currentTime); if (withComponentRegistry.isNumber(info.currentTimeLastUpdated)) { this.internalState.lastTimeUpdate = info.currentTimeLastUpdated; } if (withComponentRegistry.isNumber(info.videoLoadedFraction)) { this.onBufferedChange(info.videoLoadedFraction * this.internalState.duration); } if (withComponentRegistry.isNumber(info.volume)) this.dispatch('volume', info.volume); if (withComponentRegistry.isBoolean(info.muted)) this.dispatch('muted', info.muted); if (withComponentRegistry.isArray(info.availableQualityLevels)) { this.dispatch('playbackQualities', info.availableQualityLevels.map(q => mapYouTubePlaybackQuality(q))); } if (withComponentRegistry.isString(info.playbackQuality)) { this.dispatch('playbackQuality', mapYouTubePlaybackQuality(info.playbackQuality)); } if (withComponentRegistry.isNumber(info.playerState)) this.onPlayerStateChange(info.playerState); } render() { return (index.h("vm-embed", { embedSrc: this.embedSrc, mediaTitle: this.mediaTitle, origin: this.getOrigin(), params: this.buildParams(), decoder: network.decodeJSON, preconnections: this.getPreconnections(), onVmEmbedLoaded: this.onEmbedLoaded.bind(this), onVmEmbedMessage: this.onEmbedMessage.bind(this), onVmEmbedSrcChange: this.onEmbedSrcChange.bind(this), ref: (el) => { this.embed = el; } })); } static get watchers() { return { "cookies": ["onVideoIdChange"], "videoId": ["onVideoIdChange"], "poster": ["onCustomPosterChange"] }; } }; YouTube.style = youtubeCss; exports.vm_youtube = YouTube;