This commit is contained in:
Jaroslav Polakovič 2022-02-18 15:20:59 +01:00
Родитель 0bceb1740a
Коммит 4bb0af08e5
7 изменённых файлов: 216 добавлений и 2 удалений

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

@ -0,0 +1,100 @@
---
title: Playback Performance
description: |
Use the Media Capabilities API and the Video Playback Quality API to estimate and measure playback efficiency and smoothness.
date: February 18, 2022
length: '1:04'
video-sources:
- src: https://storage.googleapis.com/kino-assets/google-cast/manifest.mpd
type: application/dash+xml
cast: true
- src: https://storage.googleapis.com/kino-assets/google-cast/master.m3u8
type: application/x-mpegURL
video-subtitles:
- default: true
kind: captions
label: English
src: https://storage.googleapis.com/kino-assets/google-cast/cap-en.vtt
srclang: en
- default: false
kind: captions
label: Česky
src: https://storage.googleapis.com/kino-assets/google-cast/cap-cs.vtt
srclang: cz
thumbnail: https://storage.googleapis.com/kino-assets/google-cast/thumbnail.png
media-session-artwork:
- sizes: 96x96
src: https://storage.googleapis.com/kino-assets/google-cast/artwork-96x96.png
type: image/png
- sizes: 128x128
src: https://storage.googleapis.com/kino-assets/google-cast/artwork-128x128.png
type: image/png
- sizes: 192x192
src: https://storage.googleapis.com/kino-assets/google-cast/artwork-192x192.png
type: image/png
- sizes: 256x256
src: https://storage.googleapis.com/kino-assets/google-cast/artwork-256x256.png
type: image/png
- sizes: 384x384
src: https://storage.googleapis.com/kino-assets/google-cast/artwork-384x384.png
type: image/png
- sizes: 512x512
src: https://storage.googleapis.com/kino-assets/google-cast/artwork-512x512.png
type: image/png
stats: true
---
## Introduction
The simplest way of embedding a video on the web is using the HTML Video
element (`<video>`). However, every browser supports a different set of video
formats. Picking the right format will ensure your video has the highest
possible browser compatibility and is going to play in almost every situation.
## Choosing the right format
Browsers usually support multiple video formats, but there is a limited overlap
in their capabilities. If you only want to encode your videos in a single
format, the safest choice is to use an [MP4 container] encapsulating `H.264`
video and `AAC` audio.
### Basic code example
```html
<video controls>
<source src="video.mp4" type="video/mp4">
</video>
```
This simple approach is enough to get you up and running. Your video will now
play in all major web browsers. Notice the `controls` attribute that instructs
browsers to allow users to control video playback, which includes volume,
seeking, selecting captions, pause/resume playback etc.
A single video source is simple to maintain, but it gives rise to some
challenges. Users of your site are going to use different classes of devices
to watch the video. A high resolution video is going to look great on desktop,
but likely will take a long time to load on slower cellular networks.
Take the video at the top of this page as an example, which has a `1280×720`
resolution using the `H.264` codec with an effective bit rate of `1503 kb/s`.
It looks decent on desktop while being small enough to not cause stuttering on
good quality 3G networks. The video is clear, however, it's not a perfect fit
for either use case and is why you should probably provide multiple video
sources with a bitrate targeting the needs of your users.
### Example FFmpeg command
```
ffmpeg -i source.mp4 -b:v 1350k -c:v libx264 -c:a copy -filter:v "scale=1280:-1" -preset veryslow video.mp4
```
## What's Next?
Advanced codecs like `VP9` and `HEVC` generally produce smaller file sizes,
which improves the visual quality and/or experience on slower networks. Next,
we'll learn when and how to specify [multiple sources] within the HTML5
`<video>` element.
[MP4 container]: https://caniuse.com/mpeg4
[multiple sources]: /multiple-sources/

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

@ -1,6 +1,6 @@
--- ---
name: Technical APIs name: Enhancements
slug: technical-apis slug: enhancements
--- ---
Make your video applications even more robust by making use of several more advanced media APIs implemented in the browsers today. Make your video applications even more robust by making use of several more advanced media APIs implemented in the browsers today.

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

@ -137,3 +137,9 @@ export const PIP_CLASSNAME = 'picture-in-picture';
export const CAST_CLASSNAME = 'cast'; export const CAST_CLASSNAME = 'cast';
export const CAST_HAS_TARGET_NAME = 'cast-has-target'; export const CAST_HAS_TARGET_NAME = 'cast-has-target';
export const CAST_TARGET_NAME = 'cast-target-name'; export const CAST_TARGET_NAME = 'cast-target-name';
/**
* Stats overlay.
*/
export const STATS_OVERLAY_CLASSNAME = 'stats-overlay';
export const STATS_OVERLAY_DISPLAYED_CLASSNAME = 'stats-overlay-visible';

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

@ -47,4 +47,5 @@ export default [
'https://storage.googleapis.com/kino-assets/picture-in-picture/thumbnail.png', 'https://storage.googleapis.com/kino-assets/picture-in-picture/thumbnail.png',
'https://storage.googleapis.com/kino-assets/google-cast/thumbnail.png', 'https://storage.googleapis.com/kino-assets/google-cast/thumbnail.png',
'https://storage.googleapis.com/kino-assets/google-cast/thumbnail.png', 'https://storage.googleapis.com/kino-assets/google-cast/thumbnail.png',
'https://storage.googleapis.com/kino-assets/google-cast/thumbnail.png',
]; ];

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

@ -82,6 +82,36 @@ video {
display: inline; display: inline;
} }
.stats-overlay {
display: none;
position: absolute;
inset: 0;
right: auto;
bottom: auto;
max-height: 65%;
max-width: 100%;
overflow: auto;
z-index: 1;
background-color: var(--code-background);
color: var(--icon);
padding: 1em;
opacity: 0.9;
box-sizing: border-box;
}
.stats-overlay h4 {
margin-top: 1em;
margin-bottom: 0.5em;
}
.stats-overlay h4:first-child {
margin-top: 0;
}
:host(.stats-overlay-visible) .stats-overlay {
display: block;
}
@media (min-width: 720px) { @media (min-width: 720px) {
:host(.picture-in-picture) .pip-overlay { :host(.picture-in-picture) .pip-overlay {
row-gap: 32px; row-gap: 32px;
@ -89,4 +119,9 @@ video {
:host(.cast) .cast-overlay { :host(.cast) .cast-overlay {
row-gap: 32px; row-gap: 32px;
} }
.stats-overlay {
inset: 1em;
right: auto;
bottom: auto;
}
} }

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

@ -25,8 +25,12 @@ import {
CAST_TARGET_NAME, CAST_TARGET_NAME,
MEDIA_SESSION_DEFAULT_ARTWORK, MEDIA_SESSION_DEFAULT_ARTWORK,
PIP_CLASSNAME, PIP_CLASSNAME,
STATS_OVERLAY_CLASSNAME,
STATS_OVERLAY_DISPLAYED_CLASSNAME,
} from '../../constants'; } from '../../constants';
import decryptVideo from '../../utils/decryptVideo'; import decryptVideo from '../../utils/decryptVideo';
import { getMediaConfigurationVideo } from '../../utils/getMediaConfiguration';
import getDecodingInfo from '../../utils/getDecodingInfo';
export default class extends HTMLElement { export default class extends HTMLElement {
/** /**
@ -119,6 +123,7 @@ export default class extends HTMLElement {
<svg viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg" xml:space="preserve" fill-rule="evenodd" clip-rule="evenodd" stroke-linecap="round" stroke-miterlimit="10"><path d="M34.133 107.201c0-13.253-10.747-24-24-24M53.333 107.2c0-23.861-19.339-43.2-43.2-43.2" fill="none" stroke="var(--icon)" stroke-width="8"/><path d="M10.133 112.001a4.8 4.8 0 1 0 0-9.6 4.8 4.8 0 0 0 0 9.6Z" fill="var(--icon)" fill-rule="nonzero"/><path d="M5.333 49.778V32c0-5.891 4.776-10.667 10.667-10.667h96c5.891 0 10.667 4.776 10.667 10.667v64c0 5.891-4.776 10.667-10.667 10.667H72.381" fill="none" stroke="var(--icon)" stroke-width="8"/></svg> <svg viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg" xml:space="preserve" fill-rule="evenodd" clip-rule="evenodd" stroke-linecap="round" stroke-miterlimit="10"><path d="M34.133 107.201c0-13.253-10.747-24-24-24M53.333 107.2c0-23.861-19.339-43.2-43.2-43.2" fill="none" stroke="var(--icon)" stroke-width="8"/><path d="M10.133 112.001a4.8 4.8 0 1 0 0-9.6 4.8 4.8 0 0 0 0 9.6Z" fill="var(--icon)" fill-rule="nonzero"/><path d="M5.333 49.778V32c0-5.891 4.776-10.667 10.667-10.667h96c5.891 0 10.667 4.776 10.667 10.667v64c0 5.891-4.776 10.667-10.667 10.667H72.381" fill="none" stroke="var(--icon)" stroke-width="8"/></svg>
<p>Casting<span class="cast-target"> to <span class="cast-target-name"></span></span></p> <p>Casting<span class="cast-target"> to <span class="cast-target-name"></span></span></p>
</div> </div>
<div class="stats-overlay">STATS OVERLAY</div>
`; `;
while (this.internal.root.firstChild) { while (this.internal.root.firstChild) {
@ -161,6 +166,20 @@ export default class extends HTMLElement {
this.videoElement.addEventListener('play', () => { this.videoElement.addEventListener('play', () => {
if (!this.internal.mediaSessionIsInit) this.initMediaSession(); if (!this.internal.mediaSessionIsInit) this.initMediaSession();
}); });
/**
* Set up the stats overlay.
*/
if (this.internal.videoData.stats) {
this.videoElement.addEventListener(
'play',
() => this.classList.add(STATS_OVERLAY_DISPLAYED_CLASSNAME),
{ once: true },
);
this.videoElement.addEventListener('playing', this.updateStatsOverlay.bind(this));
this.videoElement.addEventListener('timeupdate', this.updateStatsOverlay.bind(this));
}
} }
/** /**
@ -510,4 +529,57 @@ export default class extends HTMLElement {
this.classList.remove(CAST_CLASSNAME); this.classList.remove(CAST_CLASSNAME);
} }
} }
/**
* Updates the video playback statistics rendered on top of the video.
*/
async updateStatsOverlay() {
let capText = '';
let vqText = '';
let mediaText = '';
const statsOverlayEl = this.internal.root.querySelector(`.${STATS_OVERLAY_CLASSNAME}`);
/** @type {VideoPlaybackQuality} */
const vq = this.videoElement.getVideoPlaybackQuality();
const vqData = [
['Total frames: ', vq.totalVideoFrames],
['Dropped frames: ', vq.droppedVideoFrames],
];
vqText = vqData.map(([label, value]) => `<div>${label}${value}</div>`).join('');
const reps = this.internal.streamer?.stream?.media?.representations;
const selectedReps = this.internal.streamer?.stream?.media?.lastRepresentationsIds;
if (reps && selectedReps) {
const videoId = selectedReps.video;
const videoRep = reps.video.find((rep) => rep.id === videoId);
if (videoRep) {
const videoConfiguration = getMediaConfigurationVideo(videoRep);
const decodingInfo = await getDecodingInfo(videoConfiguration);
const capData = [
['Power efficient: ', decodingInfo.powerEfficient],
['Smooth: ', decodingInfo.smooth],
['Supported: ', decodingInfo.supported],
];
capText = capData.map(([label, value]) => `<div>${label}${value}</div>`).join('');
const mediaData = [
['Video codec: ', videoConfiguration.video.contentType],
['Video resolution: ', `${videoConfiguration.video.width}x${videoConfiguration.video.height}`],
];
mediaText = mediaData.map(([label, value]) => `<div>${label}${value}</div>`).join('');
}
}
statsOverlayEl.innerHTML = `
<h4>Media Info</h4>
${mediaText}
<h4>Video Playback Quality API</h4>
${vqText}
<h4>Media Capabilities API</h4>
${capText}`;
}
} }