UNPKG

twitch-stream-embed

Version:

A Web Component for easily embedding a Twitch.tv livestream to any website

208 lines (178 loc) 5.99 kB
export class TwitchStream extends HTMLElement { static TWITCH_EMBED_URL = 'https://embed.twitch.tv/embed/v1.js'; static get attributes() { return { channel: {}, width: { default: 940 }, height: { default: 480 }, theme: { default: 'dark' }, muted: { default: false }, autoplay: { default: true }, chat: { default: false }, allowfullscreen: { default: true }, parent: { default: '' }, }; } constructor() { super(); this.initialized = false; this.attachShadow({ mode: 'open' }); } connectedCallback() { this.setDefaults(); this.initializeTwitchEmbed(); this.render(); } getPlayer() { return this.embed ? this.embed.getPlayer() : null; } play() { this.embed.play(); } pause() { this.embed.pause(); } getChannel() { return this.embed.getChannel(); } setChannel(channel) { this.setAttribute('channel', channel); this.embed.setChannel(channel); } getQualities() { return this.embed.getQualities(); } setQuality(quality) { const qualities = this.embed.getQualities().map(q => q.name); if (!qualities.includes(quality)) { throw Error(`Quality is not valid. Valid qualities are ${qualities.join(', ')}`); } this.embed.setQuality(quality); } getMuted() { return this.embed.getMuted(); } setMuted(muted) { if (muted) { this.setAttribute('muted', ''); } else { this.removeAttribute('muted', ''); } this.embed.setMuted(muted); } getVolume() { return this.embed.getVolume(); } setVolume(volume) { this.embed.setVolume(volume); } async initializeTwitchEmbed() { if (!window.Twitch) { await this.importTwitch(); } await this.newFrame(); let embedElem = this.shadowRoot.querySelector('#twitch-embed'); const embed = new Twitch.Embed(embedElem, { width: this.width, height: this.height, channel: this.channel, theme: this.theme, muted: this.muted, autoplay: this.autoplay, layout: this.chat ? 'video-with-chat' : 'video', allowfullscreen: this.allowfullscreen, parent: this.parent, }); this.embed = embed; this._setEmbedListeners(); this.initialized = true; } _setEmbedListeners() { this.embed.addEventListener(Twitch.Embed.VIDEO_READY, () => { this.dispatchEvent(new CustomEvent('twitch-stream.video.ready', { detail: { embed: this.embed } })); }); this.embed.addEventListener(Twitch.Embed.VIDEO_PLAY, sessionId => { this.dispatchEvent( new CustomEvent('twitch-stream.video.play', { detail: { embed: this.embed, sessionId } }), ); }); // Set all the callback events in a loop since we are just exposing them and no extra // functionality is required const events = ['ENDED', 'PAUSE', 'PLAY', 'PLAYBACK_BLOCKED', 'PLAYING', 'OFFLINE', 'ONLINE', 'READY']; events.forEach(ev => { this.embed.addEventListener(Twitch.Player[ev], async () => { await this._handlePlayingState(ev); this.dispatchEvent( new CustomEvent(`twitch-stream.${ev.toLowerCase()}`, { detail: { embed: this.embed } }), ); }); }); } async _handlePlayingState() { await this.wait(50); const isPaused = this.embed.isPaused(); if (isPaused) { this.setAttribute('paused', ''); this.removeAttribute('playing'); } else { this.setAttribute('playing', ''); this.removeAttribute('paused'); } } wait(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } newFrame() { return new Promise(resolve => window.requestAnimationFrame(resolve)); } _handleAttributeChange(attributeName) { switch (attributeName) { case 'channel': this.setChannel(this.channel); break; } } importTwitch() { return new Promise((resolve, reject) => { const script = document.createElement('script'); script.src = TwitchStream.TWITCH_EMBED_URL; document.head.appendChild(script); script.onload = resolve; script.onerror = reject; }); } render() { const content = TwitchStream.template.content.cloneNode(true); this.shadowRoot.innerHTML = ''; this.shadowRoot.appendChild(content); } static get template() { const template = document.createElement('template'); template.innerHTML = `<div id="twitch-embed" style="height: 100%"></div>`; return template; } setDefaults() { const attributes = TwitchStream.attributes; Object.keys(attributes).forEach(attr => { if (!this[attr]) { this[attr] = attributes[attr].default; } }); } attributeChangedCallback(name, oldValue, newValue) { if (oldValue === newValue) return; this[name] = newValue === '' ? true : newValue; if (this.initialized) { this._handleAttributeChange(name); } } static get observedAttributes() { const attributes = TwitchStream.attributes; return Object.keys(attributes).filter(attr => { return typeof attributes[attr].watch === 'undefined' || attributes[attr].watch; }); } } if (!customElements.get('twitch-stream')) { customElements.define('twitch-stream', TwitchStream); }