feat(demo): add web codecs decoder

This commit is contained in:
Simon Chan 2021-08-24 15:33:17 +08:00
parent 2e1e1c1b31
commit 9217178222
11 changed files with 447 additions and 197 deletions

View file

@ -42,10 +42,10 @@
"@yume-chan/ts-package-builder": "^1.0.0"
},
"dependencies": {
"@fluentui/font-icons-mdl2": "^8.1.3",
"@fluentui/react": "^8.19.1",
"@fluentui/react-file-type-icons": "^8.1.3",
"@fluentui/react-hooks": "^8.2.2",
"@fluentui/font-icons-mdl2": "^8.1.9",
"@fluentui/react": "^8.29.1",
"@fluentui/react-file-type-icons": "^8.2.3",
"@fluentui/react-hooks": "^8.2.7",
"@types/node": "^15.0.3",
"@yume-chan/adb": "^0.0.9",
"@yume-chan/adb-backend-webusb": "^0.0.9",

View file

@ -65,6 +65,16 @@ function serializePacket(packet: AdbPacketInit) {
return parts.join(' ');
}
const LoggerLine = withDisplayName('LoggerLine')(({ packet }: { packet: [string, AdbPacketInit]; }) => {
const string = useMemo(() => serializePacket(packet[1]), [packet]);
return (
<>
{packet[0]}{' '}{string}
</>
);
});
export interface LoggerContextValue {
visible: boolean;
@ -113,12 +123,18 @@ export interface LoggerProps {
logger: AdbEventLogger;
}
function shouldVirtualize(props: IListProps<string>) {
function shouldVirtualize(props: IListProps<[string, AdbPacketInit]>) {
return !!props.items && props.items.length > 100;
}
function renderCell(item?: string) {
return item;
function renderCell(item?: [string, AdbPacketInit]) {
if (!item) {
return null;
}
return (
<LoggerLine packet={item} />
);
}
export const Logger = withDisplayName('Logger')(({
@ -126,23 +142,23 @@ export const Logger = withDisplayName('Logger')(({
logger,
}: LoggerProps) => {
const contextValue = useContext(LoggerContext);
const [lines, setLines] = useState<string[]>([]);
const [packets, setPackets] = useState<[string, AdbPacketInit][]>([]);
const scrollerRef = useRef<HTMLDivElement | null>(null);
useEffect(() => {
const disposables = new DisposableList();
disposables.add(logger.onIncomingPacket((packet => {
setLines(lines => {
lines = lines.slice();
lines.push('Incoming ' + serializePacket(packet));
return lines;
setPackets(packets => {
packets = packets.slice();
packets.push(['Incoming', packet]);
return packets;
});
})));
disposables.add(logger.onOutgoingPacket(packet => {
setLines(lines => {
lines = lines.slice();
lines.push('Outgoing ' + serializePacket(packet));
return lines;
setPackets(packets => {
packets = packets.slice();
packets.push(['Outgoing', packet]);
return packets;
});
}));
return disposables.dispose;
@ -163,7 +179,7 @@ export const Logger = withDisplayName('Logger')(({
iconName: 'Copy',
},
onClick: () => {
setLines(lines => {
setPackets(lines => {
window.navigator.clipboard.writeText(lines.join('\r'));
return lines;
});
@ -176,7 +192,7 @@ export const Logger = withDisplayName('Logger')(({
iconName: 'Delete',
},
onClick: () => {
setLines([]);
setPackets([]);
},
},
], []);
@ -184,8 +200,11 @@ export const Logger = withDisplayName('Logger')(({
const mergedClassName = useMemo(() => mergeStyles(
className,
classNames['logger-container'],
!contextValue?.visible && { display: 'none' }
), [contextValue?.visible]);
), [className]);
if (!contextValue?.visible) {
return null;
}
return (
<Stack
@ -195,8 +214,7 @@ export const Logger = withDisplayName('Logger')(({
<CommandBar items={commandBarItems} />
<div ref={scrollerRef} className={classNames.grow}>
<List
items={lines}
usePageCache
items={packets}
onShouldVirtualize={shouldVirtualize}
onRenderCell={renderCell}
/>

View file

@ -0,0 +1,13 @@
import { Disposable } from "@yume-chan/event";
import { ValueOrPromise } from "@yume-chan/struct";
import { FrameSize } from "./server";
export interface Decoder extends Disposable {
configure(config: FrameSize): ValueOrPromise<void>;
decode(data: BufferSource): ValueOrPromise<void>;
}
export interface DecoderConstructor {
new(canvas: HTMLCanvasElement): Decoder;
}

View file

@ -1,17 +1,29 @@
import { Dialog, Dropdown, ICommandBarItemProps, Icon, IconButton, IDropdownOption, LayerHost, Position, ProgressIndicator, SpinButton, Stack, Toggle, TooltipHost } from '@fluentui/react';
import { useBoolean, useId } from '@fluentui/react-hooks';
import { FormEvent, KeyboardEvent, useCallback, useContext, useMemo, useRef, useState } from 'react';
import YUVBuffer from 'yuv-buffer';
import YUVCanvas from 'yuv-canvas';
import { FormEvent, KeyboardEvent, useCallback, useContext, useLayoutEffect, useMemo, useRef, useState } from 'react';
import { CommandBar, DemoMode, DeviceView, DeviceViewRef, ErrorDialogContext, ExternalLink } from '../../components';
import { CommonStackTokens } from '../../styles';
import { formatSpeed, useSpeed, withDisplayName } from '../../utils';
import { useAdbDevice } from '../type';
import { Decoder, DecoderConstructor } from "./decoder";
import { AndroidCodecLevel, AndroidCodecProfile, AndroidKeyCode, AndroidMotionEventAction, fetchServer, ScrcpyClient, ScrcpyClientOptions, ScrcpyLogLevel, ScrcpyScreenOrientation, ScrcpyServerVersion } from './server';
import { createTinyH264Decoder, TinyH264Decoder } from './tinyh264';
import { TinyH264DecoderWrapper } from "./tinyh264";
import { WebCodecsDecoder } from "./webcodecs/decoder";
const DeviceServerPath = '/data/local/tmp/scrcpy-server.jar';
const decoders: { name: string; factory: DecoderConstructor; }[] = [{
name: 'TinyH264 (Software)',
factory: TinyH264DecoderWrapper,
}];
if (typeof window.VideoDecoder === 'function') {
decoders.push({
name: 'WebCodecs',
factory: WebCodecsDecoder,
});
}
function clamp(value: number, min: number, max: number): number {
if (value < min) {
return min;
@ -30,6 +42,7 @@ export const Scrcpy = withDisplayName('Scrcpy')((): JSX.Element | null => {
const device = useAdbDevice();
const [running, setRunning] = useState(false);
const [canvasKey, setCanvasKey] = useState(decoders[0].name);
const canvasRef = useRef<HTMLCanvasElement | null>(null);
const [width, setWidth] = useState(0);
const [height, setHeight] = useState(0);
@ -57,14 +70,34 @@ export const Scrcpy = withDisplayName('Scrcpy')((): JSX.Element | null => {
const [settingsVisible, { toggle: toggleSettingsVisible }] = useBoolean(false);
const [selectedDecoder, setSelectedDecoder] = useState(decoders[0]);
const decoderRef = useRef<Decoder | undefined>(undefined);
const handleSelectedDecoderChange = useCallback((e?: FormEvent<HTMLElement>, option?: IDropdownOption) => {
if (!option) { return; }
setSelectedDecoder(option.data as { name: string; factory: DecoderConstructor; });
}, []);
useLayoutEffect(() => {
if (!running) {
// Different decoders may need different canvas context,
// but it's impossible to change context type on a canvas element,
// so re-render canvas element after stopped
setCanvasKey(selectedDecoder.name);
}
}, [running, selectedDecoder]);
const [encoders, setEncoders] = useState<string[]>([]);
const [currentEncoder, setCurrentEncoder] = useState<string>();
const handleCurrentEncoderChange = useCallback((e?: FormEvent<HTMLElement>, option?: IDropdownOption) => {
if (!option) {
if (!option) { return; }
setCurrentEncoder(option.key as string);
}, []);
const [resolution, setResolution] = useState(1080);
const handleResolutionChange = useCallback((e: any, value?: string) => {
if (value === undefined) {
return;
}
setCurrentEncoder(option.key as string);
setResolution(+value);
}, []);
const [bitRate, setBitRate] = useState(4_000_000);
@ -95,6 +128,10 @@ export const Scrcpy = withDisplayName('Scrcpy')((): JSX.Element | null => {
(async () => {
try {
if (!selectedDecoder) {
throw new Error('No available decoder');
}
setServerTotalSize(0);
setServerDownloadedSize(0);
setServerUploadedSize(0);
@ -145,7 +182,6 @@ export const Scrcpy = withDisplayName('Scrcpy')((): JSX.Element | null => {
path: DeviceServerPath,
version: ScrcpyServerVersion,
logLevel: ScrcpyLogLevel.Debug,
// TinyH264 is slow, so limit the max resolution and bit rate
maxSize: 1080,
bitRate: 4_000_000,
orientation: ScrcpyScreenOrientation.Unlocked,
@ -155,24 +191,25 @@ export const Scrcpy = withDisplayName('Scrcpy')((): JSX.Element | null => {
level: AndroidCodecLevel.Level4,
};
let encoder!: string;
setCurrentEncoder(current => {
if (current) {
encoder = current;
options.encoder = current;
return current;
} else {
encoder = encoders.find(item => item !== '') ?? 'OMX.hisi.video.encoder.avc';
return encoder;
options.encoder = encoders[0];
return encoders[0];
}
});
options.encoder = encoder;
let bitRate!: number;
setBitRate(current => {
bitRate = current;
setResolution(current => {
options.maxSize = current;
return current;
});
setBitRate(current => {
options.bitRate = current;
return current;
});
options.bitRate = bitRate;
const client = new ScrcpyClient(options);
@ -187,54 +224,24 @@ export const Scrcpy = withDisplayName('Scrcpy')((): JSX.Element | null => {
});
client.onClose(stop);
let decoderPromise: Promise<TinyH264Decoder> | undefined;
client.onSizeChanged(async ({ croppedWidth, croppedHeight, cropLeft, cropTop }) => {
let oldDecoderPromise = decoderPromise;
decoderPromise = createTinyH264Decoder();
const decoder = new selectedDecoder.factory(canvasRef.current!);
decoderRef.current = decoder;
let decoder = await oldDecoderPromise;
decoder?.dispose();
if (canvasRef.current) {
canvasRef.current.width = croppedWidth;
canvasRef.current.height = croppedHeight;
}
client.onSizeChanged(async (config) => {
const { croppedWidth, croppedHeight, } = config;
setWidth(croppedWidth);
setHeight(croppedHeight);
const yuvCanvas = YUVCanvas.attach(canvasRef.current!);
const canvas = canvasRef.current!;
canvas.width = croppedWidth;
canvas.height = croppedHeight;
decoder = await decoderPromise;
decoder.pictureReady((args) => {
const { data, width: videoWidth, height: videoHeight } = args;
const format = YUVBuffer.format({
width: videoWidth,
height: videoHeight,
chromaWidth: videoWidth / 2,
chromaHeight: videoHeight / 2,
cropLeft,
cropTop,
cropWidth: croppedWidth,
cropHeight: croppedHeight,
displayWidth: croppedWidth,
displayHeight: croppedHeight,
await decoder.configure(config);
});
const array = new Uint8Array(data);
const frame = YUVBuffer.frame(format,
YUVBuffer.lumaPlane(format, array, videoWidth, 0),
YUVBuffer.chromaPlane(format, array, videoWidth / 2, videoWidth * videoHeight),
YUVBuffer.chromaPlane(format, array, videoWidth / 2, videoWidth * videoHeight + videoWidth * videoHeight / 4)
);
yuvCanvas.drawFrame(frame);
});
});
client.onVideoData(async ({ data }) => {
let decoder = await decoderPromise;
decoder?.feed(data!);
await decoder.decode(data);
});
client.onClipboardChange(content => {
@ -244,13 +251,13 @@ export const Scrcpy = withDisplayName('Scrcpy')((): JSX.Element | null => {
await client.start();
scrcpyClientRef.current = client;
setRunning(true);
} catch (e) {
} catch (e: any) {
showErrorDialog(e.message);
} finally {
setConnecting(false);
}
})();
}, [device]);
}, [device, selectedDecoder]);
const stop = useCallback(() => {
(async () => {
@ -261,6 +268,8 @@ export const Scrcpy = withDisplayName('Scrcpy')((): JSX.Element | null => {
await scrcpyClientRef.current.close();
scrcpyClientRef.current = undefined;
decoderRef.current?.dispose();
setRunning(false);
})();
}, []);
@ -458,6 +467,7 @@ export const Scrcpy = withDisplayName('Scrcpy')((): JSX.Element | null => {
bottomHeight={40}
>
<canvas
key={canvasKey}
ref={handleCanvasRef}
style={{ display: 'block', outline: 'none' }}
tabIndex={-1}
@ -480,8 +490,27 @@ export const Scrcpy = withDisplayName('Scrcpy')((): JSX.Element | null => {
onChange={handleCurrentEncoderChange}
/>
{decoders.length > 1 && (
<Dropdown
label="Decoder"
options={decoders.map(item => ({ key: item.name, text: item.name, data: item }))}
selectedKey={selectedDecoder.name}
onChange={handleSelectedDecoderChange}
/>
)}
<SpinButton
label="Target Bit Rate"
label="Max Resolution (longer side, 0 = unlimited)"
labelPosition={Position.top}
value={resolution.toString()}
min={0}
max={2560}
step={100}
onChange={handleResolutionChange}
/>
<SpinButton
label="Max Bit Rate"
labelPosition={Position.top}
value={bitRate.toString()}
min={100}

View file

@ -5,7 +5,7 @@ import Struct from '@yume-chan/struct';
import { AndroidCodecLevel, AndroidCodecProfile } from './codec';
import { ScrcpyClientConnection, ScrcpyClientForwardConnection, ScrcpyClientReverseConnection } from "./connection";
import { AndroidKeyEventAction, AndroidMotionEventAction, ScrcpyControlMessageType, ScrcpyInjectKeyCodeControlMessage, ScrcpyInjectTextControlMessage, ScrcpyInjectTouchControlMessage, ScrcpySimpleControlMessage } from './message';
import { parse_sequence_parameter_set } from './sps';
import { parse_sequence_parameter_set, SequenceParameterSet } from './sps';
export enum ScrcpyLogLevel {
Debug = 'debug',
@ -177,7 +177,7 @@ export interface ScrcpyClientOptions {
/**
* The maximum value of both width and height.
*/
maxSize?: number;
maxSize?: number | undefined;
bitRate: number;
@ -200,21 +200,19 @@ export interface ScrcpyClientOptions {
encoder?: string;
}
interface FrameSize {
width: number;
export interface FrameSize {
sequenceParameterSet: SequenceParameterSet;
width: number;
height: number;
cropLeft: number;
cropRight: number;
cropTop: number;
cropBottom: number;
croppedWidth: number;
croppedHeight: number;
}
@ -416,16 +414,6 @@ export class ScrcpyClient {
const { width, height } = await Size.deserialize(this.videoStream);
this._screenWidth = width;
this._screenHeight = height;
this.sizeChangedEvent.fire({
width,
height,
cropLeft: 0,
cropRight: 0,
cropTop: 0,
cropBottom: 0,
croppedWidth: width,
croppedHeight: height,
});
let buffer: ArrayBuffer | undefined;
while (this._running) {
@ -435,6 +423,8 @@ export class ScrcpyClient {
}
if (pts === NoPts) {
const sequenceParameterSet = parse_sequence_parameter_set(data.slice(0));
const {
pic_width_in_mbs_minus1,
pic_height_in_map_units_minus1,
@ -443,8 +433,7 @@ export class ScrcpyClient {
frame_crop_right_offset,
frame_crop_top_offset,
frame_crop_bottom_offset,
} = parse_sequence_parameter_set(data.slice(0));
} = sequenceParameterSet;
const width = (pic_width_in_mbs_minus1 + 1) * 16;
const height = (pic_height_in_map_units_minus1 + 1) * (2 - frame_mbs_only_flag) * 16;
const cropLeft = frame_crop_left_offset * 2;
@ -458,6 +447,7 @@ export class ScrcpyClient {
this._screenHeight = screenHeight;
this.sizeChangedEvent.fire({
sequenceParameterSet,
width,
height,
cropLeft: cropLeft,

View file

@ -154,15 +154,18 @@ export function parse_sequence_parameter_set(buffer: ArrayBuffer) {
}
const profile_idc = reader.read(8);
const constraint_set0_flag = !!reader.next();
const constraint_set1_flag = !!reader.next();
const constraint_set2_flag = !!reader.next();
const constraint_set3_flag = !!reader.next();
const constraint_set4_flag = !!reader.next();
const constraint_set5_flag = !!reader.next();
const constraint_set = reader.read(8);
const constraint_set_reader = new BitReader(new Uint8Array([constraint_set]));
const constraint_set0_flag = !!constraint_set_reader.next();
const constraint_set1_flag = !!constraint_set_reader.next();
const constraint_set2_flag = !!constraint_set_reader.next();
const constraint_set3_flag = !!constraint_set_reader.next();
const constraint_set4_flag = !!constraint_set_reader.next();
const constraint_set5_flag = !!constraint_set_reader.next();
// reserved_zero_2bits
if (reader.read(2) !== 0) {
if (constraint_set_reader.read(2) !== 0) {
throw new Error('Invalid data');
}
@ -255,6 +258,7 @@ export function parse_sequence_parameter_set(buffer: ArrayBuffer) {
return {
profile_idc,
constraint_set,
constraint_set0_flag,
constraint_set1_flag,
constraint_set2_flag,
@ -276,3 +280,5 @@ export function parse_sequence_parameter_set(buffer: ArrayBuffer) {
throw new Error('Invalid data');
}
export type SequenceParameterSet = ReturnType<typeof parse_sequence_parameter_set>;

View file

@ -1,6 +1,10 @@
import { PromiseResolver } from '@yume-chan/async';
import { AutoDisposable, EventEmitter } from '@yume-chan/event';
import TinyH264Worker from 'worker-loader!./worker';
import YUVBuffer from 'yuv-buffer';
import YUVCanvas from 'yuv-canvas';
import { Decoder } from "../decoder";
import { FrameSize } from "../server";
let worker: TinyH264Worker | undefined;
let workerReady = false;
@ -88,3 +92,63 @@ export function createTinyH264Decoder(): Promise<TinyH264Decoder> {
streamId += 1;
return Promise.resolve(decoder);
}
export class TinyH264DecoderWrapper implements Decoder {
private canvas: HTMLCanvasElement;
private decoderPromise: Promise<TinyH264Decoder> | undefined;
public constructor(canvas: HTMLCanvasElement) {
this.canvas = canvas;
}
async configure(config: FrameSize): Promise<void> {
this.decoderPromise?.then(decoder => decoder.dispose());
this.decoderPromise = createTinyH264Decoder();
const decoder = await this.decoderPromise;
const { cropLeft, cropTop, croppedWidth, croppedHeight } = config;
const yuvCanvas = YUVCanvas.attach(this.canvas);
decoder.pictureReady((args) => {
const { data, width: videoWidth, height: videoHeight } = args;
const format = YUVBuffer.format({
width: videoWidth,
height: videoHeight,
chromaWidth: videoWidth / 2,
chromaHeight: videoHeight / 2,
cropLeft,
cropTop,
cropWidth: croppedWidth,
cropHeight: croppedHeight,
displayWidth: croppedWidth,
displayHeight: croppedHeight,
});
const array = new Uint8Array(data);
const frame = YUVBuffer.frame(format,
YUVBuffer.lumaPlane(format, array, videoWidth, 0),
YUVBuffer.chromaPlane(format, array, videoWidth / 2, videoWidth * videoHeight),
YUVBuffer.chromaPlane(format, array, videoWidth / 2, videoWidth * videoHeight + videoWidth * videoHeight / 4)
);
yuvCanvas.drawFrame(frame);
});
}
async decode(data: BufferSource): Promise<void> {
const decoder = await this.decoderPromise;
if (!decoder) {
throw new Error('Decoder not configured!');
}
if ('buffer' in data) {
decoder.feed(data.buffer);
} else {
decoder.feed(data);
}
}
dispose(): void {
this.decoderPromise?.then(decoder => decoder.dispose());
};
}

View file

@ -0,0 +1,49 @@
/// <reference path="web-codecs.d.ts"/>
import { ValueOrPromise } from "@yume-chan/struct";
import { Decoder } from '../decoder';
import { FrameSize } from "../server";
function toHex(value: number) {
return value.toString(16).padStart(2, '0').toUpperCase();
}
export class WebCodecsDecoder implements Decoder {
private decoder: VideoDecoder;
private context: CanvasRenderingContext2D;
public constructor(canvas: HTMLCanvasElement) {
this.context = canvas.getContext('2d')!;
this.decoder = new VideoDecoder({
output: (frame) => {
this.context.drawImage(frame, 0, 0);
frame.close();
},
error() { },
});
}
public configure(config: FrameSize): ValueOrPromise<void> {
const { sequenceParameterSet: { profile_idc, constraint_set, level_idc } } = config;
// https://www.rfc-editor.org/rfc/rfc6381#section-3.3
// ISO Base Media File Format Name Space
const codec = `avc1.${[profile_idc, constraint_set, level_idc].map(toHex).join('')}`;
this.decoder.configure({
codec: codec,
optimizeForLatency: true,
});
}
decode(data: BufferSource): ValueOrPromise<void> {
this.decoder.decode(new EncodedVideoChunk({
type: 'key',
timestamp: 0,
data,
}));
}
public dispose() {
this.decoder.close();
}
}

View file

@ -0,0 +1,81 @@
type HardwareAcceleration = "no-preference" | "prefer-hardware" | "prefer-software";
interface VideoDecoderConfig {
codec: string;
description?: BufferSource | undefined;
codedWidth?: number | undefined;
codedHeight?: number | undefined;
displayAspectWidth?: number | undefined;
displayAspectHeight?: number | undefined;
colorSpace?: VideoColorSpaceInit | undefined;
hardwareAcceleration?: HardwareAcceleration | undefined;
optimizeForLatency?: boolean | undefined;
}
interface VideoDecoderSupport {
supported: boolean;
config: VideoDecoderConfig;
}
class VideoFrame {
constructor(image: CanvasImageSource, init?: VideoFrameInit);
constructor(image: VideoFrame, init?: VideoFrameInit);
constructor(data: BufferSource, init: VideoFrameInit);
get codedWidth(): number;
get codedHeight(): number;
get displayWidth(): number;
close(): void;
}
interface CanvasDrawImage {
drawImage(image: VideoFrame, dx: number, dy: number): void;
drawImage(image: VideoFrame, dx: number, dy: number, dw: number, dh: number): void;
drawImage(image: VideoFrame, sx: number, sy: number, sw: number, sh: number, dx: number, dy: number, dw: number, dh: number): void;
}
interface VideoDecoderInit {
output: (output: VideoFrame) => void;
error: (error: DOMException) => void;
}
declare class VideoDecoder {
static isConfigSupported(config: VideoDecoderConfig): Promise<VideoDecoderSupport>;
constructor(options: VideoDecoderInit);
get state(): 'unconfigured' | 'configured' | 'closed';
get decodeQueueSize(): number;
configure(config: VideoDecoderConfig): void;
decode(chunk: EncodedVideoChunk): void;
flush(): Promise<void>;
reset(): void;
close(): void;
}
type EncodedVideoChunkType = 'key' | 'delta';
interface EncodedVideoChunkInit {
type: EncodedVideoChunkType;
timestamp: number;
duration?: number | undefined;
data: BufferSource;
}
class EncodedVideoChunk {
constructor(init: EncodedVideoChunkInit);
get type(): EncodedVideoChunkType;
get timestamp(): number;
get duration(): number | undefined;
get byteLength(): number;
copyTo(destination: BufferSource): void;
}
declare interface Window {
VideoDecoder: typeof VideoDecoder;
EncodedVideoChunk: typeof EncodedVideoChunk,
}

View file

@ -1,10 +1,10 @@
dependencies:
'@docusaurus/core': 2.0.0-beta.3_8b2c375de1f48daa4c6dacdf427b888f
'@docusaurus/preset-classic': 2.0.0-beta.3_74e645be279ffc7336acf83b89df8e82
'@fluentui/font-icons-mdl2': 8.1.6_b2f98f1a44929e460196386bafc39b6a
'@fluentui/react': 8.23.0_feb38eca05fb6b816d8b1d044961447a
'@fluentui/react-file-type-icons': 8.2.0_b2f98f1a44929e460196386bafc39b6a
'@fluentui/react-hooks': 8.2.4_b2f98f1a44929e460196386bafc39b6a
'@fluentui/font-icons-mdl2': 8.1.9_b2f98f1a44929e460196386bafc39b6a
'@fluentui/react': 8.29.1_feb38eca05fb6b816d8b1d044961447a
'@fluentui/react-file-type-icons': 8.2.3_b2f98f1a44929e460196386bafc39b6a
'@fluentui/react-hooks': 8.2.7_b2f98f1a44929e460196386bafc39b6a
'@mdx-js/react': 1.6.22_react@17.0.2
'@rush-temp/adb': file:projects/adb.tgz
'@rush-temp/adb-backend-webusb': file:projects/adb-backend-webusb.tgz
@ -2066,37 +2066,37 @@ packages:
webpack-cli: '*'
resolution:
integrity: sha512-DApc6xcb3CvvsBCfRU6Zk3KoZa4mZfCJA4XRv5zhlhaSb0GFuAo7KQ353RUu6d0eYYylY3GGRABXkxRE1SEClA==
/@fluentui/date-time-utilities/8.2.1:
/@fluentui/date-time-utilities/8.2.2:
dependencies:
'@fluentui/set-version': 8.1.3
'@fluentui/set-version': 8.1.4
tslib: 2.3.0
dev: false
resolution:
integrity: sha512-0AYXaXFQ3bPsOtiOi3bJSihzf+w3L44iQK38EiQgp3uAXei/i36VtDCToHZehYV+eS4s1qb/QGksoL0F4G6WCQ==
/@fluentui/dom-utilities/2.1.3:
integrity: sha512-djHrX/38ty+F93qLQjzmRzPzK598CW9g/RPhQH6GyrFBLPSWM1swYKB5TP6E7FrIf+fT4pVqrNUSYZhgi2rrOQ==
/@fluentui/dom-utilities/2.1.4:
dependencies:
'@fluentui/set-version': 8.1.3
'@fluentui/set-version': 8.1.4
tslib: 2.3.0
dev: false
resolution:
integrity: sha512-i2YECSldnkzPAhVmrxksuKSbqBKfMbHrexGqDJm7dy6KoIx+JSilbA5Lz0YNhA7VEgCy1X01GHlWBvqhCNQW8g==
/@fluentui/font-icons-mdl2/8.1.6_b2f98f1a44929e460196386bafc39b6a:
integrity: sha512-+gsAnEjgoKB37o+tsMdSLtgqZ9z2PzpvnHx/2IqhRWjQQd7Xc7MbQsbZaQ5qfkioFHLnWGc/+WORpqKPy/sWrg==
/@fluentui/font-icons-mdl2/8.1.9_b2f98f1a44929e460196386bafc39b6a:
dependencies:
'@fluentui/set-version': 8.1.3
'@fluentui/style-utilities': 8.2.0_b2f98f1a44929e460196386bafc39b6a
'@fluentui/set-version': 8.1.4
'@fluentui/style-utilities': 8.3.0_b2f98f1a44929e460196386bafc39b6a
tslib: 2.3.0
dev: false
peerDependencies:
'@types/react': '*'
react: '*'
resolution:
integrity: sha512-tNAaX72NQYbvR9zeiOiVQBQhYtVgPUgh68LKTGywuYGc2WffBu20Xk9wII8iLGmAijLI1QClaCQxaRAL0IkfBA==
/@fluentui/foundation-legacy/8.1.6_b2f98f1a44929e460196386bafc39b6a:
integrity: sha512-kRf14aaw/sAFl+eC6KWY0aaAI7zJAoYfWrikRZXi6yuMv/R8EJcuvYHUK1i3+LllX0wqVNJVGGwNlGXS8eMciw==
/@fluentui/foundation-legacy/8.1.9_b2f98f1a44929e460196386bafc39b6a:
dependencies:
'@fluentui/merge-styles': 8.1.3
'@fluentui/set-version': 8.1.3
'@fluentui/style-utilities': 8.2.0_b2f98f1a44929e460196386bafc39b6a
'@fluentui/utilities': 8.2.1_b2f98f1a44929e460196386bafc39b6a
'@fluentui/merge-styles': 8.1.4
'@fluentui/set-version': 8.1.4
'@fluentui/style-utilities': 8.3.0_b2f98f1a44929e460196386bafc39b6a
'@fluentui/utilities': 8.3.0_b2f98f1a44929e460196386bafc39b6a
'@types/react': 17.0.14
react: 17.0.2
tslib: 2.3.0
@ -2105,24 +2105,24 @@ packages:
'@types/react': '>=16.8.0 <18.0.0'
react: '>=16.8.0 <18.0.0'
resolution:
integrity: sha512-TJzUFcpfcJefXNmTOAJBKgIlQXDPw/dIcpO9l2nBfORvy4RnrJK4QjpdJPp5XOhDPtDVjlKPB1WvavoRkPRrbg==
/@fluentui/keyboard-key/0.3.3:
integrity: sha512-jy6dqIBYIv+vTdQ0BJNqn9Je3SrmnrFAUHxxwn1QkFEYf9kIykqzF8Mt45osHER0SmWpSrqGOeGrkGKtki2vrA==
/@fluentui/keyboard-key/0.3.4:
dependencies:
tslib: 2.3.0
dev: false
resolution:
integrity: sha512-3qX1WNCgJlKq7uGH76rLC4cdESgwdLhMH9WjcQUkQNJKtBpL4vs5O99M1keEhd3pfooW7zasr6AcYcWq4BRB4g==
/@fluentui/merge-styles/8.1.3:
integrity: sha512-pVY2m3IC5+LLmMzsaPApX9eKTzpOzdgQwrR3FNTE6mGx3N/+QWYM7fdF+T1ldZQt87dCRSeQnmAo5kqjtxeA/w==
/@fluentui/merge-styles/8.1.4:
dependencies:
'@fluentui/set-version': 8.1.3
'@fluentui/set-version': 8.1.4
tslib: 2.3.0
dev: false
resolution:
integrity: sha512-5vZUyXnbOb9M1rMLzQ7Kj5uadHgSTsp3gv0xDv6bfPvzB9RgQa3dEuJ6TA34tLezw8sFYuA6NnKd57nlb4aXMA==
/@fluentui/react-file-type-icons/8.2.0_b2f98f1a44929e460196386bafc39b6a:
integrity: sha512-zCAEjZyALk0CGW1H9YNJU+e/MW0P5sFJfrDvac27K4S/dIQvKnOwMUNOWRkNz3yUEt0R9vo0NtiO3cW04cZq3A==
/@fluentui/react-file-type-icons/8.2.3_b2f98f1a44929e460196386bafc39b6a:
dependencies:
'@fluentui/set-version': 8.1.3
'@fluentui/style-utilities': 8.2.0_b2f98f1a44929e460196386bafc39b6a
'@fluentui/set-version': 8.1.4
'@fluentui/style-utilities': 8.3.0_b2f98f1a44929e460196386bafc39b6a
'@types/react': 17.0.14
react: 17.0.2
tslib: 2.3.0
@ -2131,14 +2131,14 @@ packages:
'@types/react': '>=16.8.0 <18.0.0'
react: '>=16.8.0 <18.0.0'
resolution:
integrity: sha512-y6d06CQdyZSfnfkhmwNYXDXeBd9kKoS+fDkoNMtZmgUWWWtb152cZXfIvIzOoE55z5DjqAKAClJuikbUvm512w==
/@fluentui/react-focus/8.1.7_b2f98f1a44929e460196386bafc39b6a:
integrity: sha512-1Mei/iSGwKmWrGMJpLG5EShhIb7TNKYFu30A/2FPqdF1muciJ7HM9/NoCv/i/B0kz9pBUxHejbUO287Xlg1v0w==
/@fluentui/react-focus/8.1.11_b2f98f1a44929e460196386bafc39b6a:
dependencies:
'@fluentui/keyboard-key': 0.3.3
'@fluentui/merge-styles': 8.1.3
'@fluentui/set-version': 8.1.3
'@fluentui/style-utilities': 8.2.0_b2f98f1a44929e460196386bafc39b6a
'@fluentui/utilities': 8.2.1_b2f98f1a44929e460196386bafc39b6a
'@fluentui/keyboard-key': 0.3.4
'@fluentui/merge-styles': 8.1.4
'@fluentui/set-version': 8.1.4
'@fluentui/style-utilities': 8.3.0_b2f98f1a44929e460196386bafc39b6a
'@fluentui/utilities': 8.3.0_b2f98f1a44929e460196386bafc39b6a
'@types/react': 17.0.14
react: 17.0.2
tslib: 2.3.0
@ -2147,12 +2147,12 @@ packages:
'@types/react': '>=16.8.0 <18.0.0'
react: '>=16.8.0 <18.0.0'
resolution:
integrity: sha512-Aa0xYN0uvPZydmJgYurT2adaEMMky48s0DX9pn9IrFIayOIDVJRbSziQgaSyQ/HHAnth4JgnfxcGRJuJEGd1jA==
/@fluentui/react-hooks/8.2.4_b2f98f1a44929e460196386bafc39b6a:
integrity: sha512-tPWSqvWONm+guQFaeBpX9E9u6D9mJzIHscx7jsswh4SAtx0/KfcDEa0w/rYOZnriz4+kY7M8+wo0wwzkDI5I1Q==
/@fluentui/react-hooks/8.2.7_b2f98f1a44929e460196386bafc39b6a:
dependencies:
'@fluentui/react-window-provider': 2.1.3_b2f98f1a44929e460196386bafc39b6a
'@fluentui/set-version': 8.1.3
'@fluentui/utilities': 8.2.1_b2f98f1a44929e460196386bafc39b6a
'@fluentui/react-window-provider': 2.1.4_b2f98f1a44929e460196386bafc39b6a
'@fluentui/set-version': 8.1.4
'@fluentui/utilities': 8.3.0_b2f98f1a44929e460196386bafc39b6a
'@types/react': 17.0.14
react: 17.0.2
tslib: 2.3.0
@ -2161,10 +2161,10 @@ packages:
'@types/react': '>=16.8.0 <18.0.0'
react: '>=16.8.0 <18.0.0'
resolution:
integrity: sha512-qc/j0YdxC0zAWVqh8BJppZuK3o9/rfyu5psY4N/AL9dmKrTFWszRgTSB5uiRShN99L88UUEV9RtlfknnLDGrUg==
/@fluentui/react-window-provider/2.1.3_b2f98f1a44929e460196386bafc39b6a:
integrity: sha512-eky46Uvy7jB27PmBBuFCvB9V9sXxrlZGcLuzDVhBWn1h3zOgtmu0txTrGYfMG/eOOlR9zf/EdDqc0F/jcQpslA==
/@fluentui/react-window-provider/2.1.4_b2f98f1a44929e460196386bafc39b6a:
dependencies:
'@fluentui/set-version': 8.1.3
'@fluentui/set-version': 8.1.4
'@types/react': 17.0.14
react: 17.0.2
tslib: 2.3.0
@ -2173,20 +2173,20 @@ packages:
'@types/react': '>=16.8.0 <18.0.0'
react: '>=16.8.0 <18.0.0'
resolution:
integrity: sha512-3NWL3Kkqp3elD/aTrUaEufRUrN7K7hYsXEsOY2bDCDjPadLGtZlTGWNYFbUYNsaL/v79gZHhH+voCECP85HqRg==
/@fluentui/react/8.23.0_feb38eca05fb6b816d8b1d044961447a:
integrity: sha512-RztmJ7ol2eMDr3NCs2OcAA1cQjZdPPUEa4aurgh4Aq+JM/BiY0aK6S4SeFtVD7F8Q7PBOz/xwOG4HlnSMQtlsg==
/@fluentui/react/8.29.1_feb38eca05fb6b816d8b1d044961447a:
dependencies:
'@fluentui/date-time-utilities': 8.2.1
'@fluentui/font-icons-mdl2': 8.1.6_b2f98f1a44929e460196386bafc39b6a
'@fluentui/foundation-legacy': 8.1.6_b2f98f1a44929e460196386bafc39b6a
'@fluentui/merge-styles': 8.1.3
'@fluentui/react-focus': 8.1.7_b2f98f1a44929e460196386bafc39b6a
'@fluentui/react-hooks': 8.2.4_b2f98f1a44929e460196386bafc39b6a
'@fluentui/react-window-provider': 2.1.3_b2f98f1a44929e460196386bafc39b6a
'@fluentui/set-version': 8.1.3
'@fluentui/style-utilities': 8.2.0_b2f98f1a44929e460196386bafc39b6a
'@fluentui/theme': 2.1.4_b2f98f1a44929e460196386bafc39b6a
'@fluentui/utilities': 8.2.1_b2f98f1a44929e460196386bafc39b6a
'@fluentui/date-time-utilities': 8.2.2
'@fluentui/font-icons-mdl2': 8.1.9_b2f98f1a44929e460196386bafc39b6a
'@fluentui/foundation-legacy': 8.1.9_b2f98f1a44929e460196386bafc39b6a
'@fluentui/merge-styles': 8.1.4
'@fluentui/react-focus': 8.1.11_b2f98f1a44929e460196386bafc39b6a
'@fluentui/react-hooks': 8.2.7_b2f98f1a44929e460196386bafc39b6a
'@fluentui/react-window-provider': 2.1.4_b2f98f1a44929e460196386bafc39b6a
'@fluentui/set-version': 8.1.4
'@fluentui/style-utilities': 8.3.0_b2f98f1a44929e460196386bafc39b6a
'@fluentui/theme': 2.2.2_b2f98f1a44929e460196386bafc39b6a
'@fluentui/utilities': 8.3.0_b2f98f1a44929e460196386bafc39b6a
'@microsoft/load-themed-styles': 1.10.193
'@types/react': 17.0.14
'@types/react-dom': 17.0.9
@ -2200,19 +2200,19 @@ packages:
react: '>=16.8.0 <18.0.0'
react-dom: '>=16.8.0 <18.0.0'
resolution:
integrity: sha512-N68C+2enQVH/TrNhyt6red6sn3KfJ63dIW/HrMiqTFxFLrFxm0aZ41VWaH1a/rMl7yDqyHIOUMKaUtvxKIItWw==
/@fluentui/set-version/8.1.3:
integrity: sha512-Au9r3WaeHirTDs++UndFjKzrAhG+HmDeiiyUQn8rPp3Z5LAcOzD3gDyINuVzSTLRQTSDL2Jn0FG5H5eB2f+Nzw==
/@fluentui/set-version/8.1.4:
dependencies:
tslib: 2.3.0
dev: false
resolution:
integrity: sha512-QYLFBnwa6xJj0phEy6r+iO5bXWXxkhS1uNngUwfWsHaTskDa4PXDDdjZAXjTVV935xqmoM4GZhZk+Tcoe/OaXA==
/@fluentui/style-utilities/8.2.0_b2f98f1a44929e460196386bafc39b6a:
integrity: sha512-2otMyJ+s+W+hjBD4BKjwYKKinJUDeIKYKz93qKrrJS0i3fKfftNroy9dHFlIblZ7n747L334plLi3bzQO1bnvA==
/@fluentui/style-utilities/8.3.0_b2f98f1a44929e460196386bafc39b6a:
dependencies:
'@fluentui/merge-styles': 8.1.3
'@fluentui/set-version': 8.1.3
'@fluentui/theme': 2.1.4_b2f98f1a44929e460196386bafc39b6a
'@fluentui/utilities': 8.2.1_b2f98f1a44929e460196386bafc39b6a
'@fluentui/merge-styles': 8.1.4
'@fluentui/set-version': 8.1.4
'@fluentui/theme': 2.2.2_b2f98f1a44929e460196386bafc39b6a
'@fluentui/utilities': 8.3.0_b2f98f1a44929e460196386bafc39b6a
'@microsoft/load-themed-styles': 1.10.193
tslib: 2.3.0
dev: false
@ -2220,12 +2220,12 @@ packages:
'@types/react': '*'
react: '*'
resolution:
integrity: sha512-gdAgBnevDOHbgqAKCaQG4CXN6dONMg8BRSZNqha0I9WdgLJy7F7t4xVo8elPjlDUP72ciYT8J9Z/YNljZzbE0w==
/@fluentui/theme/2.1.4_b2f98f1a44929e460196386bafc39b6a:
integrity: sha512-NGcT7XiEWR4LbtiPV9900N6DhQKRdG1cJm0efA1pUk640XxVFD0nnR/RmQFPtC3bkDRcVv7jK8E/0SSlRPkkbw==
/@fluentui/theme/2.2.2_b2f98f1a44929e460196386bafc39b6a:
dependencies:
'@fluentui/merge-styles': 8.1.3
'@fluentui/set-version': 8.1.3
'@fluentui/utilities': 8.2.1_b2f98f1a44929e460196386bafc39b6a
'@fluentui/merge-styles': 8.1.4
'@fluentui/set-version': 8.1.4
'@fluentui/utilities': 8.3.0_b2f98f1a44929e460196386bafc39b6a
'@types/react': 17.0.14
react: 17.0.2
tslib: 2.3.0
@ -2234,12 +2234,12 @@ packages:
'@types/react': '>=16.8.0 <18.0.0'
react: '>=16.8.0 <18.0.0'
resolution:
integrity: sha512-Y4FWgnYldvAFOo24tfsREMb8/3Tn5uDoYgGO7AAdrksP7VAaavaEVQCOgvHWy3l89Bsxf00/fE+QJ/AHv5Z4CA==
/@fluentui/utilities/8.2.1_b2f98f1a44929e460196386bafc39b6a:
integrity: sha512-aR/Kn8B/ch/CYDwKWMv/fXrTaRXoQj86AhhmOOuq3GR3f4Qw53js7SaQMPJ6K/Ii6OS8chNmy+xelrPAqZStYA==
/@fluentui/utilities/8.3.0_b2f98f1a44929e460196386bafc39b6a:
dependencies:
'@fluentui/dom-utilities': 2.1.3
'@fluentui/merge-styles': 8.1.3
'@fluentui/set-version': 8.1.3
'@fluentui/dom-utilities': 2.1.4
'@fluentui/merge-styles': 8.1.4
'@fluentui/set-version': 8.1.4
'@types/react': 17.0.14
react: 17.0.2
tslib: 2.3.0
@ -2248,7 +2248,7 @@ packages:
'@types/react': '>=16.8.0 <18.0.0'
react: '>=16.8.0 <18.0.0'
resolution:
integrity: sha512-ezRkBUDhHQjrqAWA6H1TwWU3STauW/EjthDAe/upJbmXeP3ynn7tTpx1gpNi/vHjJlRkO1JjNoiSc6P4MZWkYw==
integrity: sha512-fOvYjUtwDrj0SoZXphbXLclCyrgiJCxkBU4z15g/HaD8nwhndwc5msjllxMO0BWWeEh0CEEbUn61DqPGMot/wQ==
/@hapi/hoek/9.2.0:
dev: false
resolution:
@ -12023,10 +12023,10 @@ packages:
version: 0.0.0
file:projects/demo.tgz:
dependencies:
'@fluentui/font-icons-mdl2': 8.1.6_b2f98f1a44929e460196386bafc39b6a
'@fluentui/react': 8.23.0_feb38eca05fb6b816d8b1d044961447a
'@fluentui/react-file-type-icons': 8.2.0_b2f98f1a44929e460196386bafc39b6a
'@fluentui/react-hooks': 8.2.4_b2f98f1a44929e460196386bafc39b6a
'@fluentui/font-icons-mdl2': 8.1.9_b2f98f1a44929e460196386bafc39b6a
'@fluentui/react': 8.29.1_feb38eca05fb6b816d8b1d044961447a
'@fluentui/react-file-type-icons': 8.2.3_b2f98f1a44929e460196386bafc39b6a
'@fluentui/react-hooks': 8.2.7_b2f98f1a44929e460196386bafc39b6a
'@types/jest': 26.0.24
'@types/node': 15.14.2
'@types/react': 17.0.14
@ -12063,7 +12063,7 @@ packages:
dev: false
name: '@rush-temp/demo'
resolution:
integrity: sha512-VU1LLuuT2kNzzKa1qdoUMFuCSWWXPldFAxSfc6f+OilE3pLlI0hHPm13fwGfEIYRwh7xBgXr1iWmIj0QORtlXA==
integrity: sha512-bFFC7rW/EaQt7ouSCVIq+y7daLZSS2QrS9kIILQd/xCNnKOmGL7RWxzzpYSKTq1+bODT0rOEtZeEFUOq/MVkNw==
tarball: file:projects/demo.tgz
version: 0.0.0
file:projects/event.tgz:
@ -12131,10 +12131,10 @@ packages:
specifiers:
'@docusaurus/core': ^2.0.0-beta.0
'@docusaurus/preset-classic': ^2.0.0-beta.0
'@fluentui/font-icons-mdl2': ^8.1.3
'@fluentui/react': ^8.19.1
'@fluentui/react-file-type-icons': ^8.1.3
'@fluentui/react-hooks': ^8.2.2
'@fluentui/font-icons-mdl2': ^8.1.9
'@fluentui/react': ^8.29.1
'@fluentui/react-file-type-icons': ^8.2.3
'@fluentui/react-hooks': ^8.2.7
'@mdx-js/react': ^1.6.21
'@rush-temp/adb': file:./projects/adb.tgz
'@rush-temp/adb-backend-webusb': file:./projects/adb-backend-webusb.tgz

View file

@ -101,7 +101,7 @@ export class AdbPacketDispatcher extends AutoDisposable {
return;
}
this.errorEvent.fire(e);
this.errorEvent.fire(e as Error);
}
}