improve fech load

This commit is contained in:
SrGooglo 2022-12-14 19:10:57 +00:00
parent 128f09a973
commit 6f1fca0279
2 changed files with 182 additions and 91 deletions

View File

@ -1,6 +1,7 @@
import React from "react" import React from "react"
import * as antd from "antd" import * as antd from "antd"
import classnames from "classnames"
import Livestream from "models/livestream" import Livestream from "models/livestream"
import { FloatingPanel } from "antd-mobile" import { FloatingPanel } from "antd-mobile"
@ -19,9 +20,12 @@ const floatingPanelAnchors = [160, 72 + 119, window.innerHeight * 0.8]
export default class StreamViewer extends React.Component { export default class StreamViewer extends React.Component {
state = { state = {
isEnded: false, requestedUsername: null,
sourceLoading: true,
isEnded: false,
loading: true,
streamSources: null,
streamInfo: null, streamInfo: null,
spectators: 0, spectators: 0,
@ -40,12 +44,14 @@ export default class StreamViewer extends React.Component {
} }
attachDecoder = { attachDecoder = {
flv: (source) => { flv: async (source) => {
if (!source) { if (!source) {
console.error("Stream source is not defined") console.error("Stream source is not defined")
return false return false
} }
this.toogleLoading(true)
const decoderInstance = mpegts.createPlayer({ const decoderInstance = mpegts.createPlayer({
type: "flv", type: "flv",
isLive: true, isLive: true,
@ -53,9 +59,14 @@ export default class StreamViewer extends React.Component {
url: source url: source
}) })
decoderInstance.on(mpegts.Events.ERROR, this.onSourceEnd)
decoderInstance.attachMediaElement(this.videoPlayerRef.current) decoderInstance.attachMediaElement(this.videoPlayerRef.current)
decoderInstance.load() decoderInstance.load()
decoderInstance.play()
await decoderInstance.play()
this.toogleLoading(false)
return decoderInstance return decoderInstance
}, },
@ -102,8 +113,25 @@ export default class StreamViewer extends React.Component {
} }
} }
onSourceEnd = () => {
if (typeof this.state.decoderInstance?.destroy === "function") {
this.state.decoderInstance.destroy()
}
this.state.player.destroy()
this.setState({
isEnded: true,
loading: false,
})
}
loadStreamInfo = async (username) => { loadStreamInfo = async (username) => {
const streamInfo = await Livestream.getLivestream({ username }) const streamInfo = await Livestream.getStreamInfo({ username }).catch((error) => {
console.error(error)
return null
})
if (!streamInfo) { if (!streamInfo) {
return false return false
@ -113,44 +141,66 @@ export default class StreamViewer extends React.Component {
this.setState({ this.setState({
streamInfo: streamInfo, streamInfo: streamInfo,
spectators: streamInfo.connectedClients, })
}
loadStreamSources = async (username) => {
const streamSources = await Livestream.getLivestream({ username }).catch((error) => {
console.error(error)
this.onSourceEnd(error)
return null
})
if (!streamSources) {
return false
}
console.log("Stream sources", streamSources)
this.setState({
streamSources: streamSources,
spectators: streamSources.connectedClients,
}) })
} }
componentDidMount = async () => { componentDidMount = async () => {
this.enterPlayerAnimation()
const requestedUsername = this.props.match.params.key const requestedUsername = this.props.match.params.key
// get stream info const player = new Plyr("#player", {
await this.loadStreamInfo(requestedUsername) clickToPlay: false,
autoplay: true,
controls: ["mute", "volume", "fullscreen", "airplay", "options", "settings",],
settings: ["quality"],
...this.state.plyrOptions,
})
if (this.state.streamInfo) { await this.setState({
if (!this.state.streamInfo.sources) { requestedUsername,
player,
})
// get stream info
this.loadStreamInfo(requestedUsername)
await this.loadStreamSources(requestedUsername)
if (this.state.streamSources) {
if (!this.state.streamSources.sources) {
console.error("Stream sources is not defined") console.error("Stream sources is not defined")
return false return false
} }
this.enterPlayerAnimation() await this.loadDecoder("flv", this.state.streamSources.sources.flv)
const player = new Plyr("#player", {
clickToPlay: false,
autoplay: true,
controls: ["mute", "volume", "fullscreen", "airplay", "options", "settings",],
settings: ["quality"],
...this.state.plyrOptions,
})
await this.setState({
player,
})
await this.loadDecoder("flv", this.state.streamInfo.sources.flv)
} }
// set a interval to update the stream info // set a interval to update the stream info
this.streamInfoInterval = setInterval(() => { this.streamInfoInterval = setInterval(() => {
this.loadStreamInfo(requestedUsername) this.loadStreamSources(requestedUsername)
}, 1000 * 60 * 3) }, 1000 * 60)
} }
componentWillUnmount = () => { componentWillUnmount = () => {
@ -203,8 +253,6 @@ export default class StreamViewer extends React.Component {
this.setState({ decoderInstance: null }) this.setState({ decoderInstance: null })
} }
this.toogleSourceLoading(true)
console.log(`Switching decoder to: ${decoder}`) console.log(`Switching decoder to: ${decoder}`)
const decoderInstance = await this.attachDecoder[decoder](...args) const decoderInstance = await this.attachDecoder[decoder](...args)
@ -213,78 +261,93 @@ export default class StreamViewer extends React.Component {
decoderInstance: decoderInstance decoderInstance: decoderInstance
}) })
this.toogleSourceLoading(false)
return decoderInstance return decoderInstance
} }
toogleSourceLoading = (to) => { toogleLoading = (to) => {
this.setState({ sourceLoading: to ?? !this.state.sourceLoading }) this.setState({ loading: to ?? !this.state.loading })
}
onSourceEnded = () => {
console.log("Source ended")
this.setState({ isEnded: true })
} }
render() { render() {
if (!this.state.streamInfo || this.state.isEnded) {
return <div className="stream_end">
<antd.Result>
<h1>
This stream is ended
</h1>
</antd.Result>
</div>
}
return <div className="livestream"> return <div className="livestream">
<div className="livestream_player"> <div className="livestream_player">
<div className="livestream_player_header"> <div className="livestream_player_header">
<div className="livestream_player_header_user"> {
<UserPreview username={this.state.streamInfo?.username} /> this.state.streamInfo
? <>
<div className="livestream_player_header_user">
<UserPreview username={this.state.requestedUsername} />
<div className="livestream_player_header_user_spectators"> {
<antd.Tag !this.state.isEnded && <div className="livestream_player_header_user_spectators">
icon={<Icons.Eye />} <antd.Tag
> icon={<Icons.Eye />}
{this.state.spectators} >
</antd.Tag> {this.state.spectators}
</div> </antd.Tag>
</div> </div>
}
</div>
<div className="livestream_player_header_info"> <div className="livestream_player_header_info">
<div className="livestream_player_header_info_title"> <div className="livestream_player_header_info_title">
<h1>{this.state.streamInfo?.info.title}</h1> <h1>{this.state.streamInfo?.title}</h1>
</div> </div>
<div className="livestream_player_header_info_description"> <div className="livestream_player_header_info_description">
<Ticker <Ticker
mode="smooth" mode="smooth"
> >
{({ index }) => { {({ index }) => {
return <h4>{this.state.streamInfo?.info.description}</h4> return <h4>{this.state.streamInfo?.description}</h4>
}} }}
</Ticker> </Ticker>
</div> </div>
</div> </div>
</>
: <antd.Skeleton active />
}
</div> </div>
<video ref={this.videoPlayerRef} id="player" /> <video
ref={this.videoPlayerRef}
id="player"
style={{
display: this.state.isEnded ? "none" : "block",
}}
/>
{
this.state.isEnded && <antd.Result>
<h1>
This stream is ended
</h1>
</antd.Result>
}
<div
className={classnames(
"livestream_player_loading",
{
["active"]: this.state.loading,
}
)}
>
<antd.Spin />
</div>
</div> </div>
{ {
window.isMobile ? window.isMobile
<FloatingPanel anchors={floatingPanelAnchors}> ? <FloatingPanel anchors={floatingPanelAnchors}>
<UserPreview username={this.state.streamInfo?.username} /> <UserPreview username={this.state.requestedUsername} />
</FloatingPanel> : </FloatingPanel>
<div className="livestream_panel"> : <div className="livestream_panel">
<div className="chatbox"> <div className="chatbox">
<div className="chatbox_header"> <div className="chatbox_header">
<h4><Icons.MessageCircle /> Live chat</h4> <h4><Icons.MessageCircle /> Live chat</h4>
</div> </div>
<LiveChat <LiveChat
roomId={`livestream/${this.state.streamInfo?.username}`} roomId={`livestream:${this.state.requestedUsername}`}
/> />
</div> </div>
</div> </div>

View File

@ -7,18 +7,9 @@
//justify-content: space-between; //justify-content: space-between;
} }
.stream_end {
position: absolute;
z-index: 100;
background-color: rgba(0, 0, 0, 0.8);
top: 0;
left: 0;
width: 100%;
height: 100vh;
}
.livestream { .livestream {
position: relative;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
@ -46,7 +37,42 @@
display: flex; display: flex;
position: relative; position: relative;
align-items: center;
justify-content: center;
width: calc(100% - @panel-width); width: calc(100% - @panel-width);
height: 100%;
overflow-y: hidden;
.livestream_player_loading {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
position: absolute;
z-index: 90;
width: 100%;
height: 100%;
backdrop-filter: blur(10px);
transition: all 0.3s ease-in-out;
opacity: 0;
pointer-events: none;
&.active {
opacity: 1;
}
}
video {
z-index: 80;
}
.livestream_player_header { .livestream_player_header {
display: flex; display: flex;
@ -87,6 +113,8 @@
.livestream_player_header_user { .livestream_player_header_user {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
margin-right: 20px;
.livestream_player_header_user_spectators { .livestream_player_header_user_spectators {
margin-top: 10px; margin-top: 10px;