mirror of
https://github.com/ragestudio/comty.git
synced 2025-06-10 19:14:16 +00:00
rewrited live player
This commit is contained in:
parent
2ed179e918
commit
8c67d78d45
@ -2,47 +2,92 @@ import React from "react"
|
|||||||
import config from "config"
|
import config from "config"
|
||||||
import * as antd from "antd"
|
import * as antd from "antd"
|
||||||
import { Icons } from "components/Icons"
|
import { Icons } from "components/Icons"
|
||||||
import moment from "moment"
|
|
||||||
|
|
||||||
import Plyr from "plyr"
|
import Plyr from "plyr"
|
||||||
import Hls from "hls.js"
|
import Hls from "hls.js"
|
||||||
import mpegts from "mpegts.js"
|
|
||||||
|
|
||||||
import "plyr/dist/plyr.css"
|
import "plyr/dist/plyr.css"
|
||||||
import "./index.less"
|
import "./index.less"
|
||||||
|
|
||||||
const streamsSource = config.remotes.streamingApi
|
|
||||||
|
|
||||||
export default class StreamViewer extends React.Component {
|
export default class StreamViewer extends React.Component {
|
||||||
state = {
|
state = {
|
||||||
isEnded: false,
|
isEnded: false,
|
||||||
isLoading: true,
|
sourceLoading: true,
|
||||||
endResult: false,
|
endResult: false,
|
||||||
|
|
||||||
userData: null,
|
userData: null,
|
||||||
streamInfo: null,
|
streamInfo: null,
|
||||||
spectators: 0,
|
spectators: 0,
|
||||||
timeFromNow: "00:00:00",
|
|
||||||
|
|
||||||
player: null,
|
player: null,
|
||||||
streamKey: null,
|
streamKey: null,
|
||||||
streamSource: null,
|
loadedDecoder: "hls",
|
||||||
loadedProtocol: "flv",
|
decoderInstance: null,
|
||||||
protocolInstance: null,
|
|
||||||
plyrOptions: {},
|
plyrOptions: {},
|
||||||
}
|
}
|
||||||
|
|
||||||
videoPlayerRef = React.createRef()
|
videoPlayerRef = React.createRef()
|
||||||
|
|
||||||
componentDidMount = async () => {
|
playerDecoderEvents = {
|
||||||
window.StreamViewer = {
|
[Hls.Events.FPS_DROP]: (event, data) => {
|
||||||
updateQuality: this.updateQuality,
|
console.log("FPS_DROP Detected", data)
|
||||||
switchProtocol: this.switchProtocol,
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
attachDecoder = {
|
||||||
|
hls: (source) => {
|
||||||
|
if (!source) {
|
||||||
|
console.error("Stream source is not defined")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
this.toogleSourceLoading(true)
|
||||||
|
|
||||||
|
const hlsInstance = new Hls({
|
||||||
|
autoStartLoad: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
hlsInstance.attachMedia(this.videoPlayerRef.current)
|
||||||
|
|
||||||
|
hlsInstance.on(Hls.Events.MEDIA_ATTACHED, () => {
|
||||||
|
hlsInstance.loadSource(source)
|
||||||
|
|
||||||
|
hlsInstance.on(Hls.Events.MANIFEST_PARSED, (event, data) => {
|
||||||
|
console.log(`${data.levels.length} quality levels found`)
|
||||||
|
|
||||||
|
this.toogleSourceLoading(false)
|
||||||
|
|
||||||
|
// try auto play
|
||||||
|
this.videoPlayerRef.current.play()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
hlsInstance.on(Hls.Events.ERROR, function (event, data) {
|
||||||
|
console.error(event, data)
|
||||||
|
|
||||||
|
switch (data.details) {
|
||||||
|
case Hls.ErrorDetails.FRAG_LOAD_ERROR: {
|
||||||
|
console.error(`Error loading fragment ${data.frag.url}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// register player decoder events
|
||||||
|
Object.keys(this.playerDecoderEvents).forEach(event => {
|
||||||
|
hlsInstance.on(event, this.playerDecoderEvents[event])
|
||||||
|
})
|
||||||
|
|
||||||
|
this.setState({ decoderInstance: hlsInstance, loadedDecoder: "hls" })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount = async () => {
|
||||||
const requestedUsername = this.props.match.params.key
|
const requestedUsername = this.props.match.params.key
|
||||||
|
|
||||||
const source = `${streamsSource}/${requestedUsername}`
|
|
||||||
const player = new Plyr("#player", {
|
const player = new Plyr("#player", {
|
||||||
autoplay: true,
|
autoplay: true,
|
||||||
controls: ["play", "mute", "volume", "fullscreen", "options", "settings"],
|
controls: ["play", "mute", "volume", "fullscreen", "options", "settings"],
|
||||||
@ -52,41 +97,42 @@ export default class StreamViewer extends React.Component {
|
|||||||
await this.setState({
|
await this.setState({
|
||||||
player,
|
player,
|
||||||
streamKey: requestedUsername,
|
streamKey: requestedUsername,
|
||||||
streamSource: source,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
await this.loadWithProtocol[this.state.loadedProtocol]()
|
// TODO: Get sourceUri from server
|
||||||
|
const sourceUri = `${config.remotes.streamingApi}/${this.state.streamKey}/index.m3u8`
|
||||||
|
|
||||||
|
await this.loadDecoder("hls", sourceUri)
|
||||||
|
|
||||||
|
// enter player
|
||||||
|
this.enterPlayerAnimation()
|
||||||
|
|
||||||
|
// fetch user info in the background
|
||||||
|
this.gatherUserInfo()
|
||||||
|
|
||||||
|
// fetch stream info in the background
|
||||||
|
//await this.gatherStreamInfo()
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount = () => {
|
||||||
|
this.exitPlayerAnimation()
|
||||||
|
}
|
||||||
|
|
||||||
|
enterPlayerAnimation = () => {
|
||||||
// make the interface a bit confortable for a video player
|
// make the interface a bit confortable for a video player
|
||||||
app.style.applyVariant("dark")
|
app.style.applyVariant("dark")
|
||||||
|
|
||||||
app.eventBus.emit("style.compactMode", true)
|
app.eventBus.emit("style.compactMode", true)
|
||||||
|
|
||||||
app.SidebarController.toggleVisibility(false)
|
app.SidebarController.toggleVisibility(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 = () => {
|
exitPlayerAnimation = () => {
|
||||||
app.style.applyVariant(app.settings.get("themeVariant"))
|
app.style.applyVariant(app.settings.get("themeVariant"))
|
||||||
|
|
||||||
app.eventBus.emit("style.compactMode", false)
|
app.eventBus.emit("style.compactMode", false)
|
||||||
|
|
||||||
app.SidebarController.toggleVisibility(true)
|
app.SidebarController.toggleVisibility(true)
|
||||||
|
|
||||||
if (this.timerCounterInterval) {
|
|
||||||
this.timerCounterInterval = clearInterval(this.timerCounterInterval)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
gatherStreamInfo = async () => {
|
gatherStreamInfo = async () => {
|
||||||
@ -121,21 +167,6 @@ export default class StreamViewer extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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) => {
|
||||||
if (loadedProtocol === "hls") {
|
if (loadedProtocol === "hls") {
|
||||||
this.state.protocolInstance.levels.forEach((level, levelIndex) => {
|
this.state.protocolInstance.levels.forEach((level, levelIndex) => {
|
||||||
@ -150,99 +181,33 @@ export default class StreamViewer extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switchProtocol = (protocol) => {
|
loadDecoder = async (decoder, ...args) => {
|
||||||
if (typeof this.state.protocolInstance.destroy === "function") {
|
if (typeof this.attachDecoder[decoder] === "undefined") {
|
||||||
this.state.protocolInstance.destroy()
|
console.error("Protocol not supported")
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({ protocolInstance: null })
|
// check if decoder is already loaded
|
||||||
|
if (this.state.decoderInstance) {
|
||||||
|
if (typeof this.state.decoderInstance.destroy === "function") {
|
||||||
|
this.state.decoderInstance.destroy()
|
||||||
|
}
|
||||||
|
|
||||||
console.log("Switching to " + protocol)
|
this.setState({ decoderInstance: null })
|
||||||
this.loadWithProtocol[protocol]()
|
}
|
||||||
|
|
||||||
|
console.log("Switching to " + decoder)
|
||||||
|
|
||||||
|
return await this.attachDecoder[decoder](...args)
|
||||||
}
|
}
|
||||||
|
|
||||||
loadWithProtocol = {
|
toogleSourceLoading = (to) => {
|
||||||
hls: () => {
|
this.setState({ sourceLoading: to ?? !this.state.sourceLoading })
|
||||||
const source = `${streamsSource}/media/${this.state.streamKey}/index.m3u8`
|
|
||||||
const hls = new Hls()
|
|
||||||
|
|
||||||
hls.loadSource(source)
|
|
||||||
hls.attachMedia(this.videoPlayerRef.current)
|
|
||||||
|
|
||||||
hls.on(Hls.Events.MANIFEST_PARSED, () => {
|
|
||||||
const levels = hls.levels
|
|
||||||
|
|
||||||
const quality = {
|
|
||||||
default: levels[levels.length - 1].height,
|
|
||||||
options: levels.map((level) => level.height),
|
|
||||||
forced: true,
|
|
||||||
onChange: (newQuality) => {
|
|
||||||
console.log('changes', newQuality)
|
|
||||||
|
|
||||||
levels.forEach((level, levelIndex) => {
|
|
||||||
if (level.height === newQuality) {
|
|
||||||
hls.currentLevel = levelIndex
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
plyrOptions: {
|
|
||||||
quality,
|
|
||||||
...this.state.plyrOptions
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
this.setState({ protocolInstance: hls, loadedProtocol: "hls" })
|
|
||||||
},
|
|
||||||
flv: () => {
|
|
||||||
const source = `${streamsSource}/stream/flv/${this.state.streamKey}`
|
|
||||||
|
|
||||||
const instance = mpegts.createPlayer({
|
|
||||||
enableWorker: true,
|
|
||||||
type: "flv",
|
|
||||||
url: source,
|
|
||||||
cors: true,
|
|
||||||
isLive: true
|
|
||||||
})
|
|
||||||
|
|
||||||
instance.attachMediaElement(this.videoPlayerRef.current)
|
|
||||||
instance.load()
|
|
||||||
instance.play()
|
|
||||||
|
|
||||||
instance.on("error", (error) => {
|
|
||||||
console.error(error)
|
|
||||||
antd.message.error(error.toString())
|
|
||||||
|
|
||||||
//this.onSourceEnded(error)
|
|
||||||
})
|
|
||||||
|
|
||||||
// instance.on("onSourceOpen", this.onSourceLoading)
|
|
||||||
// instance.on(mpegts.Events["LOADING_COMPLETE"], this.onSourceLoadingDone)
|
|
||||||
// instance.on("onSourceEnded", this.onSourceEnded)
|
|
||||||
|
|
||||||
this.setState({ protocolInstance: instance, loadedProtocol: "flv" })
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
onSourceLoading = () => {
|
|
||||||
this.setState({
|
|
||||||
isLoading: true,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
onSourceLoadingDone = () => {
|
|
||||||
console.log(`loaded ${this.state.loadedProtocol}`)
|
|
||||||
this.setState({
|
|
||||||
isLoading: false,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onSourceEnded = () => {
|
onSourceEnded = () => {
|
||||||
console.log("Source ended")
|
console.log("Source ended")
|
||||||
|
|
||||||
this.setState({ isEnded: true })
|
this.setState({ isEnded: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user