yume-chan / ya-webadb

ADB in your browser
https://tangoapp.dev/
MIT License
2.32k stars 305 forks source link

Uncaught (in promise) TypeError: Cannot read private member #dispatcher from an object whose class did not declare it #594

Closed meetAndEgg closed 1 year ago

meetAndEgg commented 1 year ago

Issue Checklist

Library version

0.0.20

Runtime

npm@9.8.0; vite@4.4.9; Microsoft Edge 115.0.1901.203 (正式版本) (64 位)

Device

NaN

Describe the bug

the following codes works well, but when I try to integrate this code into one class , an error Uncaught (in promise) TypeError: Cannot read private member #dispatcher from an object whose class did not declare it happens.

const CredentialStore = new AdbWebCredentialStore()
let backend: AdbDaemonWebUsbDevice | undefined
const div = ref()
const decoder = new WebCodecsDecoder(ScrcpyVideoCodecId.H264)
onMounted(() => {
    div.value.appendChild(decoder.renderer)
})

const handleClick = async () => {
    if (!AdbDaemonWebUsbDeviceManager.BROWSER) {
        alert('WebUSB is not supported in this browser')
        return
    }

    if (!backend) {
        backend = await AdbDaemonWebUsbDeviceManager.BROWSER.requestDevice() // 设备选择
        if (!backend) {
            alert('No device selected')
            return
        }
    }

    const connection = await backend.connect()

    // tell user to accept connection on device
    const device = new Adb(
        await AdbDaemonTransport.authenticate({
            serial: backend.serial,
            connection,
            credentialStore: CredentialStore,
        })
    )

    console.log('push server file to phone...')
    console.log(SCRCPY_SERVER_BUFFER)

    await AdbScrcpyClient.pushServer(
        device,
        new ReadableStream<Consumable<Uint8Array>>({
            start(controller) {
                controller.enqueue(
                    new Consumable(new Uint8Array(SCRCPY_SERVER_BUFFER))
                )
                controller.close()
            },
        })
    ).catch((e: AdbScrcpyExitedError) => {
        console.log(e.output)
    })
    console.log('start server...')
    const client = await AdbScrcpyClient.start(
        device,
        DEFAULT_SERVER_PATH,
        // If server binary was downloaded manually, must provide the correct version
        SCRCPY_SERVER_VERSION,
        new AdbScrcpyOptions2_0(
            new ScrcpyOptions2_0({
                audio: false,
                control: false,
            })
        )
    ).catch((e: AdbScrcpyExitedError) => {
        console.log(e.output)
    })
    if (!client) {
        return
    }
    const { metadata: videoMetadata, stream: videoPacketStream } =
        await client.videoStream
    videoPacketStream // from `@yume-chan/scrcpy`
        .pipeTo(decoder.writable)
}

the code of my class:

class ScpyClient {
    // backend: AdbDaemonWebUsbDevice | undefined
    adb: Adb | undefined
    client: AdbScrcpyClient | void | undefined
    logs: string[] = [`scrcpy version:${SCRCPY_SERVER_VERSION}`]
    parent: Ref<HTMLElement> | undefined
    decoder: WebCodecsDecoder | undefined
    constructor(parent: Ref<HTMLElement>) {
        this.parent = parent
    }
    get connected() {
        return this.client != undefined
    }
    async connectDevice() {
        if (!this.adb) {
            this.disconnect()
        }

        if (!AdbDaemonWebUsbDeviceManager.BROWSER) {
            this.logs.push('WebUSB is not supported in this browser')
            return
        }
        const backend =
            await AdbDaemonWebUsbDeviceManager.BROWSER.requestDevice() // 设备选择
        if (!backend) {
            this.logs.push('No device selected')
            return
        }
        console.log(backend)
        const connection = await backend.connect()

        this.adb = new Adb(
            await AdbDaemonTransport.authenticate({
                serial: backend.serial,
                connection,
                credentialStore: new AdbWebCredentialStore(),
            })
        )
        this.adb.disconnected.then(() => {
            this.logs.push('adb disconnected')
        })
        this.logs.push('please accept connection on device')
        this.startScrcpy()
    }

    async startScrcpy() {
        this.logs.push('push server file to phone...')
        await AdbScrcpyClient.pushServer( // error happens here
            this.adb!,
            new ReadableStream<Consumable<Uint8Array>>({
                start(controller) {
                    controller.enqueue(
                        new Consumable(new Uint8Array(SCRCPY_SERVER_BUFFER))
                    )
                    controller.close()
                },
            })
        )
        this.logs.push('start server...')
        this.client = await AdbScrcpyClient.start(
            this.adb!,
            DEFAULT_SERVER_PATH,
            // If server binary was downloaded manually, must provide the correct version
            SCRCPY_SERVER_VERSION,
            new AdbScrcpyOptions2_0(
                new ScrcpyOptions2_0({
                    audio: false,
                    control: false,
                })
            )
        ).catch((e: AdbScrcpyExitedError) => {
            if (e.output && e.output.length > 0) {
                this.logs.push(...e.output)
            }
        })
        if (!this.client) {
            return
        }
        this.client.stdout.pipeTo(
            new WritableStream<string>({
                write: (line) => {
                    this.logs.push(line)
                },
            })
        )
        this.logs.push('start decoder...')
        this.decoder = new WebCodecsDecoder(ScrcpyVideoCodecId.H264)
        this.parent!.value.appendChild(this.decoder.renderer)
        const { metadata: videoMetadata, stream: videoPacketStream } =
            await this.client.videoStream
        videoPacketStream // from `@yume-chan/scrcpy`
            .pipeTo(this.decoder.writable)
        ;(await this.client.videoStream).stream
            .pipeThrough(
                new InspectStream((packet) => {
                    if (packet.type === 'configuration') {
                        console.log(packet.data)

                        // this.width = croppedWidth
                        // this.height = croppedHeight
                        // this.onVideoResize(this.width, this.height)

                        // this.logs.push(
                        //     `[client] Video size changed: ${croppedWidth}x${croppedHeight}`
                        // )
                    }
                    // else {
                    //     this.bitRatesCount += packet.data.length
                    // }
                })
            )
            .pipeTo(this.decoder.writable)
    }
    async disconnect() {
        if (this.client) {
            this.client.close()
            this.client = undefined
        }
        if (this.adb) {
            this.adb.close()
            this.adb = undefined
        }

        if (this.decoder) {
            this.parent!.value.removeChild(this.decoder.renderer)
            this.decoder.dispose()
            this.decoder = undefined
        }
    }
}

const div = ref()
const client = reactive(new ScpyClient(div))

the error :

transport.ts:209  Uncaught (in promise) TypeError: Cannot read private member #dispatcher from an object whose class did not declare it
    at get disconnected (transport.ts:209:21)
    at Reflect.get (<anonymous>)
    at Object.get2 [as get] (reactivity.esm-bundler.js:444:25)
    at get disconnected (adb.ts:69:31)
    at Reflect.get (<anonymous>)
    at Object.get2 [as get] (reactivity.esm-bundler.js:444:25)
    at Proxy.connectDevice (scrcpyClient.ts:63:18)

As you can see, I commented out an attribute backend, because I initially attempted to use backend as a class attribute, but the same error was reported when using this.backend.connect(). After using backend as a local variable, the error disappeared.

Steps to reproduce


yume-chan commented 1 year ago

It's caused by Vue:

Maybe you can try the proposed workaround in that issue:

this.adb = toRaw(new Adb(...));
meetAndEgg commented 1 year ago

Got it!