最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Video element shows blank while the media stream is set through srcObject - Stack Overflow

programmeradmin3浏览0评论

I know this is a little long code, however, I gave complete code because I really couldn't find exact cause. The local view works perfectly, but other's users shows blank div in chrome. Please help with this, I'm stuck with this since a week.

And, as I tested, the MediaStream (event.stream) wasn't null, and is valid.

Full code:

import SendIcon from '@mui/icons-material/Send';
import GridLayout from "react-grid-layout";


import "./Video.css"

import ReactPlayer from 'react-player';

const server_url = 'http://localhost:3002/';
const beforeUnloadListener = (event) => {
    //Send something to back end
    // event.preventDefault(); // This is necessary for the confirmation dialog to show in some browsers
    // event.returnValue = 'Are you sure you want to leave?'; // This will show a confirmation dialog
    this.handleDeleteCurrUserFromDB();
    this.handleEndCall();

}



const viewTypes = [
    {
        value: 'Default',
        label: <LabelWithIcon icon={InterpreterModeIcon} text="Default View" />,
    },
    {
        value: 'GridView',
        label: <LabelWithIcon icon={GridViewIcon} text="Grid View" />,
    },

];


class Video extends Component {




    constructor(props) {
        super(props)

        this.myVidRef = React.createRef();
        this.remoteStreamsRef = React.createRef();

        this.videoContainerRef = React.createRef();


        this.localVideoref = React.createRef()

        this.videoAvailable = false
        this.audioAvailable = false

        this.state = {
            video: false,
            audio: false,
            screen: false,
            showModal: false,
            screenAvailable: false,
            messages: [],
            localStream: [],
            streams: [],
            message: "",
            newmessages: 0,
            askForUsername: true,
            username: name,
            gridRows: 1,
            meetLinkPSM: meetLinkPSMt,
            videoType: "Default", // Add this line
            remoteStreams: [],
            forceRenderKey: 0, // Force re-render when needed
            gridLayoutData: [],
            renderReloader: 0,

        }
        connections = {}

        this.getPermissions()




    }

    handleVideoTypeChange = (event) => {
        const selectedType = event.target.value;
        this.setState({ videoType: selectedType }, () => {
            console.log("Selected video type:", this.state.videoType); // Debug log
        });
        this.getUserMedia(); // Call getUserMedia after updating the video type
        console.log("Local video stream:", this.localVideoref.current?.srcObject);
        

    };
    getPermissions = async () => {
        try {
            await navigator.mediaDevices.getUserMedia({ video: true })
                .then(() => this.videoAvailable = true)
                .catch(() => this.videoAvailable = false)

            await navigator.mediaDevices.getUserMedia({ audio: true })
                .then(() => this.audioAvailable = true)
                .catch(() => this.audioAvailable = false)

            if (navigator.mediaDevices.getDisplayMedia) {
                this.setState({ screenAvailable: true })
            } else {
                this.setState({ screenAvailable: false })
            }

            if (this.videoAvailable || this.audioAvailable) {
                navigator.mediaDevices.getUserMedia({ video: this.videoAvailable, audio: this.audioAvailable })
                    .then((stream) => {
                        window.localStream = stream
                        this.localVideoref.current.srcObject = stream
                    })
                    .then((stream) => { })
                    .catch((e) => console.log(e))
            }
        } catch (e) { console.log(e) }
    }
    putfullScreen = (event) => {
        let elem = event.target
        if (elem.requestFullscreen) {
            elem.requestFullscreen()
        } else if (elem.mozRequestFullScreen) { /* Firefox */
            elem.mozRequestFullScreen()
        } else if (elem.webkitRequestFullscreen) { /* Chrome, Safari and Opera */
            elem.webkitRequestFullscreen()
        } else if (elem.msRequestFullscreen) { /* IE/Edge */
            elem.msRequestFullscreen()
        }

    }
    getMedia = () => {
        this.setState({
            video: this.videoAvailable,
            audio: this.audioAvailable
        }, () => {
            this.getUserMedia()
            this.connectToSocketServer()
        })
    }

    getUserMedia = () => {
        if ((this.state.video && this.videoAvailable) || (this.state.audio && this.audioAvailable)) {
            navigator.mediaDevices.getUserMedia({ video: this.state.video, audio: this.state.audio })
                .then(this.getUserMediaSuccess)
                .then((stream) => { })
                .catch((e) => console.log(e))
        } else {
            try {
                let tracks = this.localVideoref.current.srcObject.getTracks()
                tracks.forEach(track => track.stop())
            } catch (e) { }
        }
    }

    getUserMediaSuccess = (stream) => {
        try {
            window.localStream.getTracks().forEach(track => track.stop())
        } catch (e) { console.log(e) }

        setTimeout(() => {
            const videoElem = document.getElementById('my-video');
            if (videoElem) {
                videoElem.srcObject = stream;
                console.log("Video element found, setting srcObject");
            } else {
                console.error("Video element not found in the DOM");
            }
        }, 1000);




        this.setState({ localStream: stream });

        for (let id in connections) {
            if (id === socketId) continue

            connections[id].addStream(window.localStream)

            connections[id].createOffer().then((description) => {
                connections[id].setLocalDescription(description)
                    .then(() => {
                        socket.emit('signal', id, JSON.stringify({ 'sdp': connections[id].localDescription }))
                    })
                    .catch(e => console.log(e))
            })
        }

        stream.getTracks().forEach(track => track.onended = () => {
            this.setState({
                video: false,
                audio: false,
            }, () => {
                try {
                    let tracks = this.localVideoref.current.srcObject.getTracks()
                    tracks.forEach(track => track.stop())
                } catch (e) { console.log(e) }

                let blackSilence = (...args) => new MediaStream([this.black(...args), this.silence()])
                window.localStream = blackSilence()
                this.localVideoref.current.srcObject = window.localStream

                for (let id in connections) {
                    connections[id].addStream(window.localStream)

                    connections[id].createOffer().then((description) => {
                        connections[id].setLocalDescription(description)
                            .then(() => {
                                socket.emit('signal', id, JSON.stringify({ 'sdp': connections[id].localDescription }))
                            })
                            .catch(e => console.log(e))
                    })
                }
            })
        })
    }

    getDislayMedia = () => {
        if (this.state.screen) {
            if (navigator.mediaDevices.getDisplayMedia) {
                navigator.mediaDevices.getDisplayMedia({ video: true, audio: true })
                    .then(this.getDislayMediaSuccess)
                    .then((stream) => { })
                    .catch((e) => console.log(e))
            }
        }
    }

    getDislayMediaSuccess = (stream) => {
        try {
            window.localStream.getTracks().forEach(track => track.stop())
        } catch (e) { console.log(e) }

        window.localStream = stream
        this.localVideoref.current.srcObject = stream

        for (let id in connections) {
            if (id === socketId) continue

            connections[id].addStream(window.localStream)

            connections[id].createOffer().then((description) => {
                connections[id].setLocalDescription(description)
                    .then(() => {
                        socket.emit('signal', id, JSON.stringify({ 'sdp': connections[id].localDescription }))
                    })
                    .catch(e => console.log(e))
            })
        }

        stream.getTracks().forEach(track => track.onended = () => {
            this.setState({
                screen: false,
            }, () => {
                try {
                    let tracks = this.localVideoref.current.srcObject.getTracks()
                    tracks.forEach(track => track.stop())
                } catch (e) { console.log(e) }

                let blackSilence = (...args) => new MediaStream([this.black(...args), this.silence()])
                window.localStream = blackSilence()
                this.localVideoref.current.srcObject = window.localStream

                this.getUserMedia()
            })
        })
    }




    gotMessageFromServer = (fromId, message) => {
        var signal = JSON.parse(message)

        if (fromId !== socketId) {
            if (signal.sdp) {
                connections[fromId].setRemoteDescription(new RTCSessionDescription(signal.sdp)).then(() => {
                    if (signal.sdp.type === 'offer') {
                        connections[fromId].createAnswer().then((description) => {
                            connections[fromId].setLocalDescription(description).then(() => {
                                socket.emit('signal', fromId, JSON.stringify({ 'sdp': connections[fromId].localDescription }))
                            }).catch(e => console.log(e))
                        }).catch(e => console.log(e))
                    }
                }).catch(e => console.log(e))
            }

            if (signal.ice) {
                connections[fromId].addIceCandidate(new RTCIceCandidate(signal.ice)).catch(e => console.log(e))
            }
        }
    }



    connectToSocketServer = () => {
        socket = io.connect(server_url, { secure: false })

        socket.on('signal', this.gotMessageFromServer)

        socket.on('connect', () => {
            socket.emit('join-call', { path: window.location.href, username: this.state.username })
            socketId = socket.id



            // socket.on('chat-message', this.addMessage)
            socket.on('chat-message', (data, sender, socketIdSender) => {
                console.log("Received chat message:", data, "from:", sender, "socketId:", socketIdSender); // Debug log
                this.addMessage(data, sender, socketIdSender);
            });

            socket.on('user-left', (id) => {

                this.state.gridLayoutData = this.state.gridLayoutData.filter(item => item.i !== id); // Remove the user from the grid layout data



                // Force a re-render
                this.forceUpdate();
                let video = document.querySelector(`[data-socket="${id}"]`)
                if (video !== null) {
                    elms--
                    video.parentNode.removeChild(video)

                    // let main = document.getElementById('main')
                    // this.changeCssVideos(main)
                }
            })

            socket.on('user-joined', (id, clients) => {
                clients.forEach((client) => {

                    const socketListId = client.id;
                    socketdmd = socketListId;
                    const username = client.username; // Add this line to get the username


                    this.triggerEventMessage(username + " Joined"); // Call the function with the socketListId and username

                    connections[socketListId] = new RTCPeerConnection(peerConnectionConfig)
                    // Wait for their ice candidate       
                    connections[socketListId].onicecandidate = function (event) {
                        if (event.candidate != null) {
                            socket.emit('signal', socketListId, JSON.stringify({ 'ice': event.candidate }))
                        }
                    }


                    const userExists = this.state.gridLayoutData.some(item => item.i == socketListId);
                    if (!userExists && socketListId !== socketId) {

                        var gridData = {
                            i: socketListId,
                            x: this.state.gridRows,
                            y: 0,
                            w: 2,
                            h: 6,
                            static: true,
                            username: username, // Add the username to the grid data
                        };
                        


                        this.setState((prevState) => {
                            // Check if the user already exists in gridLayoutData

                            const updatedGridLayoutData = prevState.gridLayoutData.map((item) => {
                                if (item.i === socketListId) {
                                    return { ...item, stream }; // Update the stream property
                                }
                                return item;

                            });

                            return { gridLayoutData: updatedGridLayoutData };
                        });
                        console.log("Stream added:", stream); // Debug log






                    };




                    // Add the local video stream
                    if (window.localStream !== undefined && window.localStream !== null) {
                        // alert("Local Stream is not null");
                        connections[socketListId].addStream(window.localStream)
                    } else {
                        // alert("Local Stream is null");
                        let blackSilence = (...args) => new MediaStream([this.black(...args), this.silence()])
                        window.localStream = blackSilence()
                        connections[socketListId].addStream(window.localStream)
                    }
                })

                if (id === socketId) {
                    for (let id2 in connections) {
                        if (id2 === socketId) continue

                        try {
                            connections[id2].addStream(window.localStream)
                        } catch (e) { }

                        connections[id2].createOffer().then((description) => {
                            connections[id2].setLocalDescription(description)
                                .then(() => {
                                    socket.emit('signal', id2, JSON.stringify({ 'sdp': connections[id2].localDescription }))
                                })
                                .catch(e => console.log(e))
                        })
                    }
                }
            })
        })
    };

    silence = () => {
        let ctx = new AudioContext()
        let oscillator = ctx.createOscillator()
        let dst = oscillator.connect(ctx.createMediaStreamDestination())
        oscillator.start()
        ctx.resume()
        return Object.assign(dst.stream.getAudioTracks()[0], { enabled: false })
    }
    black = ({ width = 640, height = 480 } = {}) => {
        let canvas = Object.assign(document.createElement("canvas"), { width, height })
        canvas.getContext('2d').fillRect(0, 0, width, height)
        let stream = canvas.captureStream()
        return Object.assign(stream.getVideoTracks()[0], { enabled: false })
    }

    handleVideo = () => this.setState({ video: !this.state.video }, () => this.getUserMedia())
    handleAudio = () => this.setState({ audio: !this.state.audio }, () => this.getUserMedia())
    handleScreen = () => this.setState({ screen: !this.state.screen }, () => this.getDislayMedia())





    openChat = () => this.setState({ showModal: true, newmessages: 0 })
    closeChat = () => this.setState({ showModal: false })
    handleMessage = (e) => this.setState({ message: e.target.value })

    // addMessage = (data, sender, socketIdSender) => {
    //  this.setState(prevState => ({
    //      messages: [...prevState.messages, { "sender": sender, "data": data }],
    //  }))
    //  if (socketIdSender !== socketId) {
    //      this.setState({ newmessages: this.state.newmessages + 1 })
    //  }
    // }

    addMessage = (data, sender, socketIdSender) => {
        console.log("Adding message to state:", data, sender, socketIdSender); // Debug log
        this.setState(prevState => ({
            messages: [...prevState.messages, { sender, data }],
        }), () => {
            console.log("Updated messages state:", this.state.messages); // Debug log after state update
        });
        if (socketIdSender !== socketId) {
            this.setState({ newmessages: this.state.newmessages + 1 });
        }
    };


    handleUsername = (e) => this.setState({ username: e.target.value })

    sendMessage = () => {
        socket.emit('chat-message', this.state.message, this.state.username)
        console.log("Message sent:", this.state.message, this.state.username)
        this.setState({ message: "", sender: this.state.username })
    }


    connect = () => {
        this.setState({ askForUsername: false }, () => this.getMedia());
        this.updateDataBaseMain();
    }
    handleShowDiagForEnd = () => {
        this.setState({ openDialog: true });
    }



    render() {

        const isMobile = this.isMobileDevice(); // Check if the device is mobile



        return (
            <div>
                {this.state.askForUsername === true ?
                    <div>
                        <div style={{
                            background: "white", width: "30%", height: "auto", padding: "20px", minWidth: "400px",
                            textAlign: "center", margin: "auto", marginTop: "50px", justifyContent: "center"
                        }}>
                            <p style={{ margin: 0, fontWeight: "bold", paddingRight: "50px" }}>Please Type You Name Here!</p>
                            <Input placeholder="Your Name" value={this.state.username} onChange={e => this.handleUsername(e)} />
                            <Button variant="contained" color="primary" onClick={this.connect} style={{ margin: "20px" }}>Connect</Button>
                        </div>

                        <div style={{ justifyContent: "center", textAlign: "center", paddingTop: "40px" }}>
                            <video id="my-video" ref={this.localVideoref} autoPlay muted style={{
                                borderStyle: "solid", borderColor: "#bdbdbd", objectFit: "fill", width: "60%", height: "30%"
                            }}></video>
                        </div>
                    </div>
                    :
                    <div>

                        <LatestEvent eventMessage={this.state.eventMessage} />
                        <div className="btn-down" style={{ position: "fixed", backgroundColor: "gray", color: "whitesmoke", textAlign: "center" }}>
                            <IconButton style={{ color: "#424242" }} onClick={this.handleVideo}>
                                {(this.state.video === true) ? <VideocamIcon /> : <VideocamOffIcon />}
                            </IconButton>

                            <IconButton style={{ color: "#f44336" }} id="btnEC" onClick={this.handleShowDiagForEnd}>
                                <CallEndIcon />
                            </IconButton>

                            <IconButton style={{ color: "#424242" }} onClick={this.handleAudio}>
                                {this.state.audio === true ? <MicIcon /> : <MicOffIcon />}
                            </IconButton>

                            {this.state.screenAvailable === true ?
                                <IconButton style={{ color: "#424242" }} onClick={this.handleScreen}>
                                    {this.state.screen === true ? <ScreenShareIcon /> : <StopScreenShareIcon />}
                                </IconButton>
                                : null}

                            <Badge badgeContent={this.state.newmessages} max={999} color="secondary" onClick={this.openChat}>
                                <IconButton style={{ color: "#424242" }} onClick={this.openChat}>
                                    <ChatIcon />
                                </IconButton>
                            </Badge>
                        </div>

                        <Modal show={this.state.showModal} onHide={this.closeChat} style={{ zIndex: "999999" }}>
                            <Modal.Header closeButton={false}>
                                <Modal.Title>Chat Room</Modal.Title>
                                <CloseIcon style={{ color: "#f44336", cursor: "pointer" }} id="btnEC" onClick={this.closeChat}>
                                </CloseIcon>
                            </Modal.Header>
                            <Modal.Body style={{ overflow: "auto", overflowY: "auto", height: "400px", textAlign: "left" }} >
                                {this.state.messages.length > 0 ? this.state.messages.map((item, index) => (
                                    <div key={index} style={{ textAlign: "left" }}>
                                        <p style={{ wordBreak: "break-all" }}><b>{item.sender}</b>: {item.data}</p>
                                    </div>
                                )) : <p>No message yet</p>}
                            </Modal.Body>
                            <Modal.Footer className="div-send-msg">
                                <TextField id="filled-full-width" variant='filled' fullWidth multiline placeholder="Message" value={this.state.message} onChange={e => this.handleMessage(e)} />
                                <br />
                                <br />
                                <Button onClick={this.sendMessage} variant="contained" endIcon={<SendIcon />}>
                                    Send
                                </Button>
                                {/* <Button variant="contained" color="primary" onClick={this.sendMessage}>Send</Button> */}
                            </Modal.Footer>
                        </Modal>

                        <div className="container">
                            <div className='header'>

                                <div className='left'>


                                    <div style={{ paddingTop: "20px" }}>

                                        <InfoIcon />
                                        <Input value={this.state.meetLinkPSM} disable="true"></Input>
                                        <Button style={{
                                            backgroundColor: "#3f51b5", color: "whitesmoke", marginLeft: "20px",
                                            marginTop: "10px", width: "120px", fontSize: "10px"
                                        }} onClick={this.copyUrl}>Copy invite link</Button>

                                    </div>
                                    <div className='right'>

                                        {!isMobile && (

                                            <TextField
                                                id="outlined-select-currency"
                                                select
                                                className='selectView'
                                                style={{ right: 0 }}
                                                value={this.state.videoType}
                                                onChange={this.handleVideoTypeChange}
                                                variant="outlined"
                                            >
                                                {viewTypes.map((option) => (
                                                    <MenuItem key={option.value} value={option.value}>
                                                        {option.label}
                                                    </MenuItem>
                                                ))}
                                            </TextField>

                                        )}
                                    </div>

                                </div>

                            </div>

                            {/* <Row id="main" className="flex-container" style={{ margin: 0, padding: 0 }}>

                                <br />

                            </Row> */}

                            {this.state.videoType === "GridView" ?

                                <div>



                                    <div style={{ paddingBottom: "80px" }}>
                                        <GridLayout
                                            id="main"
                                            className="layout"
                                            layout={this.state.gridLayoutData}
                                            cols={6}
                                            rowHeight={30}
                                            width={1200}
                                            isResizable={false}
                                            isDraggable={true}
                                        >


                                            <div
                                                key="local"
                                                data-grid={{ i: "local", x: 0, y: 0, w: 2, h: 6 }}
                                                className="video-tile main-video"
                                            >

                                                <video
                                                    id="my-video"
                                                    autoPlay
                                                    ref={this.localVideoref}
                                                    muted
                                                    style={{ width: '100%', height: '100%', objectFit: 'cover' }}
                                                />
                                                <div className="video-label">
                                                    {this.state.username} (You)
                                                </div>
                                            </div>

                                            {this.state.gridLayoutData.map((item) => (
                                                <div
                                                    id={item.i}
                                                    data-grid={{ i: item.i, x: item.x, y: item.y, w: item.w, h: item.h }}
                                                    key={item.i} className="video-container"
                                                >

                                                    <>
                                                        <video
                                                            id={item.i}
                                                            autoPlay
                                                            playsInline
                                                            muted
                                                            style={{ borderColor: "#000", borderStyle: "solid", borderWidth: "1", width: "100%", height: "100%", objectFit: "cover", display: "block" }}
                                                            ref={(videoElement) => {
                                                                if (videoElement && item.stream) {
                                                                    console.log(" Video element found:", videoElement);
                                                                    console.log(" Stream for item:", item.stream);
                                                                    
                                                                    // Set attributes
                                                                    videoElement.setAttribute("data-socket", item.i);
                                                                    videoElement.setAttribute("title", item.username);
                                                                    videoElement.muted = true;
                                                                    videoElement.autoplay = true;
                                                            
                                                                    // If a different stream is set, remove the old one
                                                                    if (videoElement.srcObject !== item.stream) {
                                                                        console.log("Updating srcObject...");
                                                                        videoElement.srcObject = item.stream;
                                                                    }
                                                            
                                                                    // Debugging: Check if the stream actually has video tracks
                                                                    const videoTracks = item.stream.getVideoTracks();
                                                                    if (videoTracks.length === 0) {
                                                                        console.error(" No video tracks found in the stream!");
                                                                    } else {
                                                                        console.log(" Video track found:", videoTracks[0]);
                                                                    }
                                                            
                                                                    // Ensure the video plays when the metadata is loaded
                                                                    videoElement.onloadedmetadata = () => {
                                                                        console.log("Video metadata loaded, attempting to play...");
                                                                        videoElement.play()
                                                                            .then(() => console.log("✅ Video is playing!"))
                                                                            .catch((error) => console.error("⚠️ Video play error:", error));
                                                                    };
                                                            
                                                                    // Extra debugging: Check when a track is added or removed
                                                                    item.stream.onaddtrack = (event) => {
                                                                        console.log(" New track added:", event.track);
                                                                    };
                                                            
                                                                    item.stream.onremovetrack = (event) => {
                                                                        console.log("Track removed:", event.track);
                                                                    };
                                                                }
                                                            }}
                                                            
                                                        ></video>

                                                    </>

                                                </div>
                                            ))}




                                        </GridLayout>
                                    </div>


                                </div>


发布评论

评论列表(0)

  1. 暂无评论