This commit is contained in:
2026-06-15 22:46:12 +08:00
commit f6508eccdb
38 changed files with 3133 additions and 0 deletions

View File

@@ -0,0 +1,29 @@
export function createApi(baseUrl) {
const root = baseUrl.replace(/\/$/, '')
return {
createRoom() {
return request({ url: `${root}/api/rooms`, method: 'POST' })
},
getRoom(code, deviceId) {
return request({ url: `${root}/api/rooms/${code}?deviceId=${encodeURIComponent(deviceId)}` })
}
}
}
function request(options) {
return new Promise((resolve, reject) => {
uni.request({
...options,
success(res) {
if (res.statusCode >= 200 && res.statusCode < 300) {
resolve(res.data)
} else {
reject(new Error(typeof res.data === 'string' ? res.data : JSON.stringify(res.data)))
}
},
fail(err) {
reject(err)
}
})
})
}

View File

@@ -0,0 +1,8 @@
export function getDeviceId() {
let id = uni.getStorageSync('deviceId')
if (!id) {
id = 'dev_' + Math.random().toString(16).slice(2) + Date.now().toString(16)
uni.setStorageSync('deviceId', id)
}
return id
}

View File

@@ -0,0 +1,71 @@
export function createPlayer({ onError } = {}) {
let nativePlayer = null
try {
// Available only after the native plugin is added in HBuilderX.
nativePlayer = uni.requireNativePlugin && uni.requireNativePlugin('SyncTV-VLC')
} catch (err) {
nativePlayer = null
}
return {
hasNative: !!nativePlayer,
open(source, server) {
const url = source.url && source.url.startsWith('/api/')
? server.replace(/\/$/, '') + source.url
: source.url
if (nativePlayer) {
nativePlayer.open({
url,
headers: source.headers || {},
username: source.username || '',
password: source.password || '',
isLive: !!source.isLive
})
}
return url
},
play() {
if (nativePlayer) {
nativePlayer.play()
return true
}
return false
},
pause() {
if (nativePlayer) {
nativePlayer.pause()
return true
}
return false
},
seek(positionMs) {
if (nativePlayer) {
nativePlayer.seek({ positionMs })
return true
}
return false
},
getPosition(callback) {
if (nativePlayer) {
nativePlayer.getPosition(callback)
return
}
callback({ positionMs: 0 })
},
release() {
if (nativePlayer) {
nativePlayer.release()
}
},
error(message) {
if (onError) onError(message)
}
}
}

View File

@@ -0,0 +1,52 @@
export class SyncClient {
constructor({ onEvent, onError }) {
this.socket = null
this.onEvent = onEvent
this.onError = onError
}
connect(baseUrl, roomCode, deviceId) {
this.close()
const wsBase = baseUrl.replace(/\/$/, '').replace('http://', 'ws://').replace('https://', 'wss://')
this.socket = uni.connectSocket({
url: `${wsBase}/ws?roomCode=${encodeURIComponent(roomCode)}&deviceId=${encodeURIComponent(deviceId)}`,
complete: () => {}
})
this.socket.onMessage((message) => {
const data = JSON.parse(message.data)
this.onEvent(data.type, data.payload || {})
})
this.socket.onError((err) => this.onError(JSON.stringify(err)))
this.socket.onClose(() => this.onError('websocket closed'))
}
close() {
if (this.socket) {
this.socket.close({})
this.socket = null
}
}
setSource(source) {
this.send('setSource', source)
}
play(positionMs) {
this.send('play', { positionMs })
}
pause(positionMs) {
this.send('pause', { positionMs })
}
syncToLive() {
this.send('syncToLive', { state: 'playing', positionMs: 0, targetLatencyMs: 3000 })
}
send(type, payload) {
if (!this.socket) return
this.socket.send({
data: JSON.stringify({ type, payload })
})
}
}