Create new API entries for posts from the User Experience category.

This commit is contained in:
Jaroslav Polakovič 2022-01-31 16:20:59 +01:00
Родитель 992da15ba9
Коммит a58353f97b
4 изменённых файлов: 191 добавлений и 78 удалений

Просмотреть файл

@ -0,0 +1,48 @@
---
title: Picture-in-picture
description: |
Play your videos in a detached floating window that stays on top of other applications using the Picture-in-Picture API.
date: January 31st, 2022
length: '1:04'
video-sources:
- src: https://storage.googleapis.com/kino-assets/autoplay/manifest.mpd
type: application/dash+xml
- src: https://storage.googleapis.com/kino-assets/autoplay/master.m3u8
type: application/x-mpegURL
video-subtitles:
- default: true
kind: captions
label: English
src: https://storage.googleapis.com/kino-assets/autoplay/cap-en.vtt
srclang: en
- default: false
kind: captions
label: Česky
src: https://storage.googleapis.com/kino-assets/autoplay/cap-cs.vtt
srclang: cz
thumbnail: https://storage.googleapis.com/kino-assets/autoplay/thumbnail.png
media-session-artwork:
- sizes: 96x96
src: https://storage.googleapis.com/kino-assets/autoplay/artwork-96x96.png
type: image/png
- sizes: 128x128
src: https://storage.googleapis.com/kino-assets/autoplay/artwork-128x128.png
type: image/png
- sizes: 192x192
src: https://storage.googleapis.com/kino-assets/autoplay/artwork-192x192.png
type: image/png
- sizes: 256x256
src: https://storage.googleapis.com/kino-assets/autoplay/artwork-256x256.png
type: image/png
- sizes: 384x384
src: https://storage.googleapis.com/kino-assets/autoplay/artwork-384x384.png
type: image/png
- sizes: 512x512
src: https://storage.googleapis.com/kino-assets/autoplay/artwork-512x512.png
type: image/png
pip: true
---
## Introduction
PiP article WIP.

Просмотреть файл

@ -0,0 +1,48 @@
---
title: Google Cast
description: |
Use the Google Cast SDK and turn your application to a cast sender able to stream your media to a compatible network device.
date: January 31st, 2022
length: '1:04'
video-sources:
- src: https://storage.googleapis.com/kino-assets/autoplay/manifest.mpd
type: application/dash+xml
- src: https://storage.googleapis.com/kino-assets/autoplay/master.m3u8
type: application/x-mpegURL
video-subtitles:
- default: true
kind: captions
label: English
src: https://storage.googleapis.com/kino-assets/autoplay/cap-en.vtt
srclang: en
- default: false
kind: captions
label: Česky
src: https://storage.googleapis.com/kino-assets/autoplay/cap-cs.vtt
srclang: cz
thumbnail: https://storage.googleapis.com/kino-assets/autoplay/thumbnail.png
media-session-artwork:
- sizes: 96x96
src: https://storage.googleapis.com/kino-assets/autoplay/artwork-96x96.png
type: image/png
- sizes: 128x128
src: https://storage.googleapis.com/kino-assets/autoplay/artwork-128x128.png
type: image/png
- sizes: 192x192
src: https://storage.googleapis.com/kino-assets/autoplay/artwork-192x192.png
type: image/png
- sizes: 256x256
src: https://storage.googleapis.com/kino-assets/autoplay/artwork-256x256.png
type: image/png
- sizes: 384x384
src: https://storage.googleapis.com/kino-assets/autoplay/artwork-384x384.png
type: image/png
- sizes: 512x512
src: https://storage.googleapis.com/kino-assets/autoplay/artwork-512x512.png
type: image/png
cast: true
---
## Introduction
Google Cast article WIP.

Просмотреть файл

@ -43,4 +43,7 @@ export default [
'https://storage.googleapis.com/kino-assets/streaming-basics/thumbnail.png',
'https://storage.googleapis.com/kino-assets/efficient-formats/thumbnail.png',
'https://storage.googleapis.com/kino-assets/adaptive-streaming/thumbnail.png',
'https://storage.googleapis.com/kino-assets/autoplay/thumbnail.png',
'https://storage.googleapis.com/kino-assets/autoplay/thumbnail.png',
'https://storage.googleapis.com/kino-assets/autoplay/thumbnail.png',
];

Просмотреть файл

@ -131,91 +131,23 @@ export default class extends HTMLElement {
this.videoElement = this.internal.root.querySelector('video');
this.videoElement.addEventListener('error', this.handleVideoError.bind(this), true);
const pipButton = this.createPiPButton();
const floatingButtonsBar = this.internal.root.querySelector('.floating-buttons');
const pipButton = this.createPiPButton();
if (pipButton) {
floatingButtonsBar.appendChild(pipButton);
}
window.kinoInitGoogleCast().then((castButton) => {
floatingButtonsBar.appendChild(castButton);
if (this.internal.videoData.cast) {
window.kinoInitGoogleCast().then((castButton) => {
floatingButtonsBar.appendChild(castButton);
window.cast.framework.CastContext.getInstance().addEventListener(
window.cast.framework.CastContextEventType.SESSION_STATE_CHANGED,
async (e) => {
if (e.sessionState === 'SESSION_STARTED' || e.sessionState === 'SESSION_RESUMED') {
const castableSources = this.internal.videoData['video-sources'].filter((source) => source.cast === true);
if (!castableSources) {
/* eslint-disable-next-line no-console */
console.error('[Google Cast] The media has no source suitable for casting.');
return;
}
const castSession = window.cast.framework.CastContext.getInstance().getCurrentSession();
const mediaInfo = new window.chrome.cast.media.MediaInfo(
castableSources[0].src,
castableSources[0].type,
);
const videoThumbnail = new window.chrome.cast.Image(this.internal.videoData.thumbnail);
const metadata = new window.chrome.cast.media.GenericMediaMetadata();
metadata.title = videoData.title;
/**
* @todo Add the Media Session artwork and define image dimensions explicitly.
*/
metadata.images = [videoThumbnail];
mediaInfo.metadata = metadata;
/** @type {Array} */
const subtitles = this.internal.videoData['video-subtitles'] || [];
const defaultSubtitles = subtitles.find((subtitle) => subtitle.default);
/**
* AFAICT the Default Media Receiver doesn't implement any UI to
* select the subtitle track.
*
* We only add the subtitle track if there is a default one.
*/
if (defaultSubtitles) {
const defaultSubtitlesTrack = new window.chrome.cast.media.Track(
1,
window.chrome.cast.media.TrackType.TEXT,
);
defaultSubtitlesTrack.trackContentId = defaultSubtitles.src;
defaultSubtitlesTrack.subtype = window.chrome.cast.media.TextTrackType.SUBTITLES;
defaultSubtitlesTrack.name = defaultSubtitles.label;
defaultSubtitlesTrack.language = defaultSubtitles.srclang;
defaultSubtitlesTrack.trackContentType = 'text/vtt';
mediaInfo.tracks = [defaultSubtitlesTrack];
}
const request = new window.chrome.cast.media.LoadRequest(mediaInfo);
try {
await castSession.loadMedia(request);
} catch (error) {
/* eslint-disable-next-line no-console */
console.error(`[Google Cast] Error code: ${error}`);
return;
}
const targetName = castSession.getCastDevice().friendlyName;
this.internal.root.querySelector(`.${CAST_TARGET_NAME}`).innerText = targetName;
this.classList.toggle(CAST_HAS_TARGET_NAME, targetName);
this.classList.add(CAST_CLASSNAME);
}
if (e.sessionState === 'SESSION_ENDED') {
this.classList.remove(CAST_CLASSNAME);
}
},
);
});
window.cast.framework.CastContext.getInstance().addEventListener(
window.cast.framework.CastContextEventType.SESSION_STATE_CHANGED,
this.initCast.bind(this),
);
});
}
/**
* Set up Media Session API integration.
@ -447,6 +379,10 @@ export default class extends HTMLElement {
if (!('pictureInPictureEnabled' in document)) {
return null;
}
if (!this.internal.videoData.pip) {
return null;
}
const ENTER_PIP_SVG = '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M20.25 15v6a1.5 1.5 0 0 1-1.5 1.5H3A1.5 1.5 0 0 1 1.5 21V8.25A1.5 1.5 0 0 1 3 6.75h3.75" stroke="var(--accent)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/><path d="M9.75 3v7.5a1.5 1.5 0 0 0 1.5 1.5H21a1.5 1.5 0 0 0 1.5-1.5V3A1.5 1.5 0 0 0 21 1.5h-9.75A1.5 1.5 0 0 0 9.75 3Z" stroke="var(--accent)" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="square"/><path d="M9 18.75V15H5.25M5.25 18.75 9 15" stroke="var(--accent)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>';
const LEAVE_PIP_SVG = '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M20.25 9V3a1.5 1.5 0 0 0-1.5-1.5H3A1.5 1.5 0 0 0 1.5 3v12.75a1.5 1.5 0 0 0 1.5 1.5h3.75" stroke="var(--accent)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/><path d="M5.25 9V5.25H9M9 9 5.25 5.25" stroke="var(--accent)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/><path d="M9.75 21v-7.5a1.5 1.5 0 0 1 1.5-1.5H21a1.5 1.5 0 0 1 1.5 1.5V21a1.5 1.5 0 0 1-1.5 1.5h-9.75a1.5 1.5 0 0 1-1.5-1.5Z" stroke="var(--accent)" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="square"/></svg>';
@ -491,4 +427,82 @@ export default class extends HTMLElement {
return pipButton;
}
/**
* Initializes Google Cast functionality.
*
* @param {SessionStateEventData} e SessionStateEventData instance.
* @returns {void}
*/
async initCast(e) {
if (e.sessionState === 'SESSION_STARTED' || e.sessionState === 'SESSION_RESUMED') {
const castableSources = this.internal.videoData['video-sources'].filter((source) => source.cast === true);
if (!castableSources) {
/* eslint-disable-next-line no-console */
console.error('[Google Cast] The media has no source suitable for casting.');
return;
}
const castSession = window.cast.framework.CastContext.getInstance().getCurrentSession();
const mediaInfo = new window.chrome.cast.media.MediaInfo(
castableSources[0].src,
castableSources[0].type,
);
const videoThumbnail = new window.chrome.cast.Image(this.internal.videoData.thumbnail);
const metadata = new window.chrome.cast.media.GenericMediaMetadata();
metadata.title = this.internal.videoData.title;
/**
* @todo Add the Media Session artwork and define image dimensions explicitly.
*/
metadata.images = [videoThumbnail];
mediaInfo.metadata = metadata;
/** @type {Array} */
const subtitles = this.internal.videoData['video-subtitles'] || [];
const defaultSubtitles = subtitles.find((subtitle) => subtitle.default);
/**
* AFAICT the Default Media Receiver doesn't implement any UI to
* select the subtitle track.
*
* We only add the subtitle track if there is a default one.
*/
if (defaultSubtitles) {
const defaultSubtitlesTrack = new window.chrome.cast.media.Track(
1,
window.chrome.cast.media.TrackType.TEXT,
);
defaultSubtitlesTrack.trackContentId = defaultSubtitles.src;
defaultSubtitlesTrack.subtype = window.chrome.cast.media.TextTrackType.SUBTITLES;
defaultSubtitlesTrack.name = defaultSubtitles.label;
defaultSubtitlesTrack.language = defaultSubtitles.srclang;
defaultSubtitlesTrack.trackContentType = 'text/vtt';
mediaInfo.tracks = [defaultSubtitlesTrack];
}
const request = new window.chrome.cast.media.LoadRequest(mediaInfo);
try {
await castSession.loadMedia(request);
} catch (error) {
/* eslint-disable-next-line no-console */
console.error(`[Google Cast] Error code: ${error}`);
return;
}
const targetName = castSession.getCastDevice().friendlyName;
this.internal.root.querySelector(`.${CAST_TARGET_NAME}`).innerText = targetName;
this.classList.toggle(CAST_HAS_TARGET_NAME, targetName);
this.classList.add(CAST_CLASSNAME);
}
if (e.sessionState === 'SESSION_ENDED') {
this.classList.remove(CAST_CLASSNAME);
}
}
}