Skip to content

Commit c3866ce

Browse files
authored
Merge pull request #171 from ragestudio/dev
2 parents ef4b669 + 67d44db commit c3866ce

File tree

13 files changed

+321
-397
lines changed

13 files changed

+321
-397
lines changed

packages/app/package.json

100755100644
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@comty/app",
3-
"version": "1.44.2@alpha",
3+
"version": "1.44.3@alpha",
44
"license": "ComtyLicense",
55
"main": "electron/main",
66
"type": "module",

packages/app/src/cores/player/classes/AudioBase.js

Lines changed: 109 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -216,8 +216,47 @@ export default class AudioBase {
216216
return manifest.source
217217
}
218218

219+
handleDemuxerError = async (event) => {
220+
if (!event || !event.error) {
221+
return null
222+
}
223+
224+
if (
225+
event.error.message &&
226+
event.error.message.includes("QuotaExceededError")
227+
) {
228+
this.console.warn(
229+
"QuotaExceededError detected, attempting buffer cleanup and recovery",
230+
)
231+
232+
const currentTime = this.audio.currentTime
233+
234+
// try to clear buffers and recover
235+
if (this.demuxer) {
236+
// reset and reinitialize the demuxer
237+
this.demuxer.reset()
238+
239+
// reattach the source
240+
const manifest = this.player.queue.currentItem
241+
242+
if (manifest && manifest.dash_manifest) {
243+
await this.demuxer.attachSource(manifest.dash_manifest, currentTime)
244+
245+
// resume playback from current position
246+
this.audio.currentTime = currentTime
247+
248+
if (this.player.state.playback_status === "playing") {
249+
await this.demuxer.play()
250+
}
251+
252+
this.console.log("Successfully recovered from QuotaExceededError")
253+
}
254+
}
255+
}
256+
}
257+
219258
async createDemuxer() {
220-
// Destroy previous instance if exists
259+
// destroy previous instance if exists
221260
if (this.demuxer) {
222261
try {
223262
this.demuxer.reset()
@@ -238,59 +277,75 @@ export default class AudioBase {
238277

239278
this.demuxer.updateSettings({
240279
streaming: {
241-
//cacheInitSegments: true,
280+
//cacheInitSegments: true
242281
buffer: {
243-
bufferTimeAtTopQuality: 30,
244-
bufferTimeAtTopQualityLongForm: 60,
245-
bufferTimeDefault: 20,
246-
initialBufferLevel: 5,
247-
bufferToKeep: 10,
282+
fastSwitchEnabled: true,
283+
flushBufferAtTrackSwitch: true,
284+
resetSourceBuffersForTrackSwitch: true,
285+
bufferToKeep: 2,
286+
bufferTimeDefault: 15,
287+
bufferTimeAtTopQuality: 20,
288+
bufferTimeAtTopQualityLongForm: 25,
248289
longFormContentDurationThreshold: 300,
249-
stallThreshold: 0.5,
250-
bufferPruningInterval: 30,
251-
},
252-
abr: {
253-
initialBitrate: {
254-
audio: 128,
255-
},
256-
rules: {
257-
insufficientBufferRule: {
258-
active: true,
259-
parameters: {
260-
bufferLevel: 0.3,
261-
},
262-
},
263-
switchHistoryRule: {
264-
active: true,
265-
parameters: {
266-
sampleSize: 8,
267-
},
268-
},
269-
},
270-
throughput: {
271-
averageCalculationMode: "slidingWindow",
272-
slidingWindowSize: 20,
273-
ewmaHalfLife: 4,
274-
},
275-
},
276-
retrySettings: {
277-
maxRetries: 5,
278-
retryDelayMs: 1000,
279-
retryBackoffFactor: 2,
280-
},
281-
requests: {
282-
requestTimeout: 30000,
283-
xhrWithCredentials: false,
290+
stallThreshold: 1,
291+
bufferPruningInterval: 3,
284292
},
285293
},
286294
})
287295

288-
// Event listeners
296+
// event listeners
289297
this.demuxer.on(dashjs.MediaPlayer.events.ERROR, (event) => {
290298
console.error("Demuxer error", event)
299+
this.handleDemuxerError(event)
291300
})
292301
}
293302

303+
preventiveBufferCleanup = async () => {
304+
if (!this.demuxer) {
305+
return null
306+
}
307+
308+
try {
309+
const currentTime = this.audio.currentTime
310+
const duration = this.audio.duration
311+
const bufferCleanupThreshold = 30
312+
const buffered = this.audio.buffered
313+
314+
// clean buffers that are more than 30 seconds behind current position
315+
if (currentTime && duration && duration !== Infinity) {
316+
if (currentTime > bufferCleanupThreshold) {
317+
// clean old ones
318+
for (let i = 0; i < buffered.length; i++) {
319+
const start = buffered.start(i)
320+
const end = buffered.end(i)
321+
322+
// if this buffer range is too far behind, its a candidate for cleanup
323+
if (end < currentTime - bufferCleanupThreshold) {
324+
try {
325+
// force demuxer to prune old buffers
326+
this.demuxer.updateSettings({
327+
streaming: {
328+
buffer: {
329+
...this.demuxer.getSettings().streaming.buffer,
330+
bufferToKeep: 2,
331+
bufferPruningInterval: 1,
332+
},
333+
},
334+
})
335+
336+
this.console.log(`Cleaned buffer range: ${start}-${end}`)
337+
} catch (cleanupError) {
338+
this.console.warn("Buffer cleanup failed:", cleanupError)
339+
}
340+
}
341+
}
342+
}
343+
}
344+
} catch (error) {
345+
this.console.warn("Preventive buffer cleanup failed:", error)
346+
}
347+
}
348+
294349
timeTick = async () => {
295350
if (
296351
!this.audio ||
@@ -314,11 +369,22 @@ export default class AudioBase {
314369
)
315370
}
316371
}
372+
373+
// run preventive buffer cleanup every 10 seconds
374+
if (Math.floor(this.audio.currentTime) % 10 === 0) {
375+
await this.preventiveBufferCleanup()
376+
}
317377
}
318378

319379
async flush() {
320380
this.audio.pause()
321381
this.audio.currentTime = 0
382+
383+
// clear intervals
384+
if (typeof this._timeTickInterval !== "undefined") {
385+
clearInterval(this._timeTickInterval)
386+
}
387+
322388
await this.createDemuxer()
323389
}
324390

packages/app/src/pages/lyrics/components/text/index.jsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,15 @@ import { motion, AnimatePresence } from "motion/react"
44

55
import { usePlayerStateContext } from "@contexts/WithPlayerContext"
66

7+
import "./index.less"
8+
79
// eslint-disable-next-line
8-
const LyricsText = React.forwardRef((props, textRef) => {
10+
const LyricsText = React.forwardRef((props, forwardedRef) => {
911
const [playerState] = usePlayerStateContext()
1012

1113
const { lyrics } = props
1214

15+
const textRef = forwardedRef ?? React.useRef(null)
1316
const currentTrackId = React.useRef(null)
1417
const [syncInterval, setSyncInterval] = React.useState(null)
1518
const [currentLineIndex, setCurrentLineIndex] = React.useState(0)
@@ -131,13 +134,17 @@ const LyricsText = React.forwardRef((props, textRef) => {
131134
}
132135

133136
return (
134-
<div className="lyrics-text-wrapper">
137+
<div
138+
className={classnames("lyrics-text-wrapper", {
139+
["static"]: props.static,
140+
})}
141+
>
135142
<AnimatePresence>
136143
<motion.div
137144
ref={textRef}
138145
className="lyrics-text"
139146
animate={{
140-
opacity: visible ? 1 : 0,
147+
opacity: props.static ? 1 : visible ? 1 : 0,
141148
}}
142149
initial={{
143150
opacity: 0,
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
.lyrics-text-wrapper {
2+
z-index: 200;
3+
position: fixed;
4+
5+
bottom: 0;
6+
left: 0;
7+
8+
padding: 60px;
9+
10+
.lyrics-text {
11+
display: flex;
12+
flex-direction: column;
13+
14+
width: 600px;
15+
height: 200px;
16+
17+
padding: 20px;
18+
gap: 30px;
19+
20+
overflow: hidden;
21+
22+
background-color: rgba(var(--background-color-accent-values), 0.6);
23+
border-radius: 12px;
24+
25+
backdrop-filter: blur(5px);
26+
-webkit-backdrop-filter: blur(5px);
27+
28+
.line {
29+
font-size: 2rem;
30+
31+
opacity: 0.1;
32+
33+
margin: 0;
34+
35+
&.current {
36+
opacity: 1;
37+
}
38+
}
39+
}
40+
41+
&.static {
42+
position: relative;
43+
padding: 0;
44+
45+
.lyrics-text {
46+
width: 100%;
47+
48+
padding: 0;
49+
background-color: unset;
50+
border-radius: unset;
51+
52+
.line {
53+
font-size: 1.6rem;
54+
opacity: 0.4;
55+
56+
&.current {
57+
opacity: 1;
58+
}
59+
}
60+
}
61+
}
62+
}

packages/app/src/pages/lyrics/components/video/index.jsx

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ const LyricsVideo = React.forwardRef((props, videoRef) => {
5959
!videoRef.current ||
6060
!lyrics ||
6161
!lyrics.video_source ||
62+
lyrics.video_loop ||
6263
typeof lyrics.video_starts_at_ms === "undefined"
6364
) {
6465
stopSyncInterval()
@@ -74,9 +75,7 @@ const LyricsVideo = React.forwardRef((props, videoRef) => {
7475
const currentVideoTime =
7576
videoRef.current.currentTime - lyrics.video_starts_at_ms / 1000
7677
const maxOffset = maxLatencyInMs / 1000
77-
const currentVideoTimeDiff = Math.abs(
78-
currentVideoTime - currentTrackTime,
79-
)
78+
const currentVideoTimeDiff = Math.abs(currentVideoTime - currentTrackTime)
8079

8180
setCurrentVideoLatency(currentVideoTimeDiff)
8281

@@ -132,22 +131,21 @@ const LyricsVideo = React.forwardRef((props, videoRef) => {
132131
hls.current.attachMedia(videoElement)
133132
}
134133
hls.current.loadSource(lyrics.video_source)
135-
} else if (
136-
videoElement.canPlayType("application/vnd.apple.mpegurl")
137-
) {
134+
} else if (videoElement.canPlayType("application/vnd.apple.mpegurl")) {
138135
videoElement.src = lyrics.video_source
139136
}
140137
}
141138

142139
if (typeof lyrics.video_starts_at_ms !== "undefined") {
143-
videoElement.loop = false
140+
videoElement.loop = lyrics.video_loop ?? false
144141
syncPlayback(true)
145142
} else {
146-
videoElement.loop = true
143+
videoElement.loop = lyrics.video_loop ?? true
147144
videoElement.currentTime = 0
148145
}
149146
} else {
150147
videoElement.src = ""
148+
151149
if (hls.current) {
152150
hls.current.stopLoad()
153151
if (hls.current.media) {
@@ -186,9 +184,7 @@ const LyricsVideo = React.forwardRef((props, videoRef) => {
186184
if (playerState.playback_status === "playing") {
187185
videoElement
188186
.play()
189-
.catch((error) =>
190-
console.error("VIDEO:: Error playing video:", error),
191-
)
187+
.catch((error) => console.error("VIDEO:: Error playing video:", error))
192188
if (shouldSync) {
193189
startSyncInterval()
194190
}

0 commit comments

Comments
 (0)