fix: Increase reliability of media auto actions (e.g. play) (#1934)
Refactors media player handling entirely to make the code more consistent and less boilerplate. Add testing for media player actions. - Closes #1921 - Closes #1916
This commit is contained in:
+5
-4
@@ -4,10 +4,11 @@ export interface AudioProperties {
|
||||
}
|
||||
|
||||
// There is currently no consistent cross-browser modern way to determine if a
|
||||
// viden has audio tracks. The below will work in ~24% of browsers, but notably
|
||||
// not in Chrome. There used to be a usable `webkitAudioDecodedByteCount`
|
||||
// property, but this now seems to be consistently 0 in Chrome. This generously
|
||||
// defaults to assuming there is audio when we cannot rule it out.
|
||||
// video element has audio tracks. The below will work in ~24% of browsers, but
|
||||
// notably not in Chrome. There used to be a usable
|
||||
// `webkitAudioDecodedByteCount` property, but this now seems to be consistently
|
||||
// 0 in Chrome. This generously defaults to assuming there is audio when we
|
||||
// cannot rule it out.
|
||||
export const mayHaveAudio = (video: HTMLVideoElement & AudioProperties): boolean => {
|
||||
if (video.mozHasAudio !== undefined) {
|
||||
return video.mozHasAudio;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { AdvancedCameraCardMediaPlayer } from '../types';
|
||||
import { Timer } from './timer';
|
||||
|
||||
// The number of seconds to hide the video controls for after loading (in order
|
||||
@@ -59,30 +58,3 @@ export const hideMediaControlsTemporarily = (
|
||||
setControlsOnVideo(video, oldValue);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @param player The Advanced Camera Card Media Player object.
|
||||
* @param video An underlying video or media player upon which to call play.
|
||||
*/
|
||||
export const playMediaMutingIfNecessary = async (
|
||||
player: AdvancedCameraCardMediaPlayer,
|
||||
video?: HTMLVideoElement | AdvancedCameraCardMediaPlayer,
|
||||
): Promise<void> => {
|
||||
// If the play call fails, and the media is not already muted, mute it first
|
||||
// and then try again. This works around some browsers that prevent
|
||||
// auto-play unless the video is muted.
|
||||
if (video?.play) {
|
||||
try {
|
||||
await video.play();
|
||||
} catch (err: unknown) {
|
||||
if ((err as Error).name === 'NotAllowedError' && !player.isMuted()) {
|
||||
await player.mute();
|
||||
try {
|
||||
await video.play();
|
||||
} catch (_) {
|
||||
// Pass.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
AdvancedCameraCardMediaPlayer,
|
||||
MediaLoadedCapabilities,
|
||||
MediaLoadedInfo,
|
||||
MediaPlayerController,
|
||||
MediaTechnology,
|
||||
} from '../types.js';
|
||||
import { dispatchAdvancedCameraCardEvent } from './basic.js';
|
||||
@@ -17,7 +17,7 @@ const MEDIA_INFO_WIDTH_CUTOFF = MEDIA_INFO_HEIGHT_CUTOFF;
|
||||
export function createMediaLoadedInfo(
|
||||
source: Event | HTMLElement,
|
||||
options?: {
|
||||
player?: AdvancedCameraCardMediaPlayer;
|
||||
mediaPlayerController?: MediaPlayerController;
|
||||
capabilities?: MediaLoadedCapabilities;
|
||||
technology?: MediaTechnology[];
|
||||
},
|
||||
@@ -45,7 +45,7 @@ export function createMediaLoadedInfo(
|
||||
return {
|
||||
width: (target as HTMLCanvasElement).width,
|
||||
height: (target as HTMLCanvasElement).height,
|
||||
player: options?.player,
|
||||
mediaPlayerController: options?.mediaPlayerController,
|
||||
...options,
|
||||
};
|
||||
}
|
||||
@@ -61,7 +61,7 @@ export function dispatchMediaLoadedEvent(
|
||||
target: HTMLElement,
|
||||
source: Event | HTMLElement,
|
||||
options?: {
|
||||
player?: AdvancedCameraCardMediaPlayer;
|
||||
mediaPlayerController?: MediaPlayerController;
|
||||
capabilities?: MediaLoadedCapabilities;
|
||||
technology?: MediaTechnology[];
|
||||
},
|
||||
|
||||
+16
-4
@@ -1,16 +1,28 @@
|
||||
import { format } from 'date-fns';
|
||||
import { View } from '../view/view';
|
||||
|
||||
export const screenshotMedia = (video: HTMLVideoElement): string | null => {
|
||||
export const screenshotImage = (image: HTMLImageElement): string | null => {
|
||||
return screenshotElement(image, image.naturalWidth, image.naturalHeight);
|
||||
};
|
||||
|
||||
export const screenshotVideo = (video: HTMLVideoElement): string | null => {
|
||||
return screenshotElement(video, video.videoWidth, video.videoHeight);
|
||||
};
|
||||
|
||||
const screenshotElement = (
|
||||
src: CanvasImageSource,
|
||||
width: number,
|
||||
height: number,
|
||||
): string | null => {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = video.videoWidth;
|
||||
canvas.height = video.videoHeight;
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (!ctx) {
|
||||
return null;
|
||||
}
|
||||
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
|
||||
ctx.drawImage(src, 0, 0, canvas.width, canvas.height);
|
||||
return canvas.toDataURL('image/jpeg');
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user