update StreamViewer

This commit is contained in:
srgooglo 2022-05-12 19:28:01 +02:00
parent ec249b70db
commit b976a595df
2 changed files with 231 additions and 21 deletions

View File

@ -1,15 +1,25 @@
import React from 'react' import React from "react"
import config from "config"
import * as antd from "antd" import * as antd from "antd"
import Plyr from 'plyr' import { Icons } from "components/Icons"
import Hls from 'hls.js' import moment from "moment"
import mpegts from 'mpegts.js'
import Plyr from "plyr"
import Hls from "hls.js"
import mpegts from "mpegts.js"
import "plyr/dist/plyr.css" import "plyr/dist/plyr.css"
import "./index.less"
const streamsSource = "http://media.ragestudio.net/live" const streamsSource = config.remotes.streamingApi
export default class StreamViewer extends React.Component { export default class StreamViewer extends React.Component {
state = { state = {
userData: null,
streamInfo: null,
spectators: 0,
timeFromNow: "00:00:00",
player: null, player: null,
streamKey: null, streamKey: null,
streamSource: null, streamSource: null,
@ -22,18 +32,98 @@ export default class StreamViewer extends React.Component {
componentDidMount = async () => { componentDidMount = async () => {
const query = new URLSearchParams(window.location.search) const query = new URLSearchParams(window.location.search)
const requested = query.get("key") const requestedUsername = query.get("key")
const source = `${streamsSource}/${requested}` const source = `${streamsSource}/${requestedUsername}`
const player = new Plyr('#player') const player = new Plyr("#player", {
autoplay: true,
controls: ["play", "mute", "volume", "fullscreen", "options", "settings"],
})
await this.setState({ await this.setState({
player, player,
streamKey: requested, streamKey: requestedUsername,
streamSource: source, streamSource: source,
}) })
await this.loadWithProtocol[this.state.loadedProtocol]() await this.loadWithProtocol[this.state.loadedProtocol]()
// make the interface a bit confortable for a video player
app.ThemeController.applyVariant("dark")
app.eventBus.emit("toogleCompactMode", true)
app.SidebarController.toogleVisible(false)
app.HeaderController.toogleVisible(false)
// fetch user info in the background
this.gatherUserInfo()
// fetch stream info in the background
// await for it
await this.gatherStreamInfo()
// create timer
if (this.state.streamInfo.connectCreated) {
this.createTimerCounter()
}
}
componentWillUnmount = () => {
app.ThemeController.applyVariant(app.settings.get("themeVariant"))
app.eventBus.emit("toogleCompactMode", false)
app.SidebarController.toogleVisible(true)
app.HeaderController.toogleVisible(true)
app.HeaderController.toogleVisible(true)
if (this.timerCounterInterval) {
this.timerCounterInterval = clearInterval(this.timerCounterInterval)
}
}
gatherStreamInfo = async () => {
const result = await app.request.get.streamInfoFromUsername(undefined, {
username: this.state.streamKey,
}).catch((error) => {
console.error(error)
antd.message.error(error.message)
return false
})
if (result) {
this.setState({
streamInfo: result,
})
}
}
gatherUserInfo = async () => {
const result = await app.request.get.user(undefined, {
username: this.state.streamKey,
}).catch((error) => {
console.error(error)
antd.message.error(error.message)
return false
})
if (result) {
this.setState({
userData: result,
})
}
}
createTimerCounter = () => {
this.timerCounterInterval = setInterval(() => {
const secondsFromNow = moment().diff(moment(this.state.streamInfo.connectCreated), "seconds")
// calculate hours minutes and seconds
const hours = Math.floor(secondsFromNow / 3600)
const minutes = Math.floor((secondsFromNow - hours * 3600) / 60)
const seconds = secondsFromNow - hours * 3600 - minutes * 60
this.setState({
timeFromNow: `${hours}:${minutes}:${seconds}`,
})
}, 1000)
} }
updateQuality = (newQuality) => { updateQuality = (newQuality) => {
@ -60,10 +150,10 @@ export default class StreamViewer extends React.Component {
console.log("Switching to " + protocol) console.log("Switching to " + protocol)
this.loadWithProtocol[protocol]() this.loadWithProtocol[protocol]()
} }
loadWithProtocol = { loadWithProtocol = {
hls: () => { hls: () => {
const source = `${this.state.streamSource}.m3u8` const source = `${streamsSource}/stream/hls/${this.state.streamKey}`
const hls = new Hls() const hls = new Hls()
hls.loadSource(source) hls.loadSource(source)
@ -72,9 +162,13 @@ export default class StreamViewer extends React.Component {
this.setState({ protocolInstance: hls, loadedProtocol: "hls" }) this.setState({ protocolInstance: hls, loadedProtocol: "hls" })
}, },
flv: () => { flv: () => {
const source = `${this.state.streamSource}.flv` const source = `${streamsSource}/stream/flv/${this.state.streamKey}`
const instance = mpegts.createPlayer({ type: 'flv', url: source, isLive: true }) const instance = mpegts.createPlayer({
type: "flv",
url: source,
isLive: true
})
instance.attachMediaElement(this.videoPlayerRef.current) instance.attachMediaElement(this.videoPlayerRef.current)
instance.load() instance.load()
@ -85,15 +179,39 @@ export default class StreamViewer extends React.Component {
} }
render() { render() {
return <div> return <div className="stream">
<antd.Select
onChange={(value) => this.switchProtocol(value)}
value={this.state.loadedProtocol}
>
<antd.Select.Option value="hls">HLS</antd.Select.Option>
<antd.Select.Option value="flv">FLV</antd.Select.Option>
</antd.Select>
<video ref={this.videoPlayerRef} id="player" /> <video ref={this.videoPlayerRef} id="player" />
<div className="panel">
<div className="info">
<div className="title">
<div>
<antd.Avatar
shape="square"
src={this.state.userData?.avatar}
/>
</div>
<div>
<h2>{this.state.userData?.username}</h2>
</div>
</div>
<div id="spectatorCount">
<Icons.Eye />
{this.state.spectators}
</div>
<div id="timeCount">
<Icons.Clock />
{this.state.timeFromNow}
</div>
</div>
<div className="chatbox">
{/* TODO: Use chatbox component and join to stream channel using username */}
<antd.Result>
<h1>
Cannot connect with chat server
</h1>
</antd.Result>
</div>
</div>
</div> </div>
} }
} }

View File

@ -0,0 +1,92 @@
.plyr__controls {
width: 100%;
display: inline-flex;
//justify-content: space-between;
}
.stream {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
height: 100vh;
width: 100vw;
color: var(--background-color-contrast);
h1,
h2,
h3,
h4,
h5,
span,
p {
color: var(--background-color-contrast);
}
.panel {
display: flex;
flex-direction: column;
height: 100vh;
width: 20vw;
.info {
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: flex-start;
width: 100%;
height: 10vh;
padding: 10px;
backdrop-filter: 20px;
h1,
h2,
h3,
h4,
h5 {
margin: 0;
}
>div {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
height: fit-content;
margin-bottom: 8px;
>div {
margin-right: 8px;
}
}
}
.chatbox {
width: 20vw;
padding: 20px;
height: 100vh;
}
#spectatorCount {
font-size: 0.8em;
}
#timeCount {
font-size: 0.8em;
}
}
.plyr {
border-radius: 0 4px 4px 0;
width: 80vw;
height: 100vh;
}
}