mirror of
https://github.com/yume-chan/ya-webadb.git
synced 2025-10-05 10:49:24 +02:00
feat(demo): add web codecs decoder
This commit is contained in:
parent
2e1e1c1b31
commit
9217178222
11 changed files with 447 additions and 197 deletions
|
@ -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",
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
|
|
13
apps/demo/src/routes/scrcpy/decoder.ts
Normal file
13
apps/demo/src/routes/scrcpy/decoder.ts
Normal 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;
|
||||
}
|
|
@ -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}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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>;
|
||||
|
|
|
@ -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());
|
||||
};
|
||||
}
|
||||
|
|
49
apps/demo/src/routes/scrcpy/webcodecs/decoder.ts
Normal file
49
apps/demo/src/routes/scrcpy/webcodecs/decoder.ts
Normal 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();
|
||||
}
|
||||
}
|
81
apps/demo/src/routes/scrcpy/webcodecs/web-codecs.d.ts
vendored
Normal file
81
apps/demo/src/routes/scrcpy/webcodecs/web-codecs.d.ts
vendored
Normal 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,
|
||||
}
|
168
common/config/rush/pnpm-lock.yaml
generated
168
common/config/rush/pnpm-lock.yaml
generated
|
@ -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
|
||||
|
|
|
@ -101,7 +101,7 @@ export class AdbPacketDispatcher extends AutoDisposable {
|
|||
return;
|
||||
}
|
||||
|
||||
this.errorEvent.fire(e);
|
||||
this.errorEvent.fire(e as Error);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue