player: workaround for autoplay

adds autostart=true/false and nosound=true/false to the query params
shows the player element when autoplay is off, so that the user can
manually click on it.
master
Florens Douwes 5 years ago
parent 9a564e9844
commit e8453967a8
  1. 7
      backend/src/Room.ts
  2. 2
      frontend/src/components/Centurion.tsx
  3. 58
      frontend/src/components/Player.ts
  4. 110
      frontend/src/components/Player.tsx
  5. 4
      frontend/src/css/player.sass
  6. 14
      frontend/src/lib/Connection.ts

@ -24,6 +24,7 @@ export default class Room {
// For debugging purposes // For debugging purposes
speedFactor = 1; speedFactor = 1;
autoStart = false;
constructor(name: number) { constructor(name: number) {
this.id = name; this.id = name;
@ -59,6 +60,12 @@ export default class Room {
this.setLeader(user); this.setLeader(user);
} }
if (this.autoStart) {
this.seekTime = 2500000;
this.running = true;
this.start();
}
this.sync(); this.sync();
} }

@ -16,12 +16,12 @@ const Centurion = () => {
const feedContent = ( const feedContent = (
<React.Fragment> <React.Fragment>
<Player/>
<Row> <Row>
<NextShot/> <NextShot/>
<Feed/> <Feed/>
<ShotsTaken/> <ShotsTaken/>
</Row> </Row>
<Player/>
</React.Fragment> </React.Fragment>
); );

@ -1,58 +0,0 @@
import {roomTime, useRoomRunningAndReadyChanged, useRoomTime, useTimelineSongFileChanged} from "../lib/Connection";
import {useRef, useState} from "react";
const Player = () => {
const roomRunning = useRoomRunningAndReadyChanged();
const _ = useRoomTime()
const timeline = useTimelineSongFileChanged();
const player = useRef(timeline ? new Audio(timeline.songFile) : null);
const [timesSeeked, setTimesSeeked] = useState(0);
// If our time synchronisation algorithm thing thinks the time is off by more
// than this value, we seek the running player to correct it.
const diffSecondsRequiredToSeekRunningPlayer = 0.20;
// Hard cap we are allowed to seek this player. Some browsers are slow or inaccurate
// and will always be off. To avoid endless skipping of the song this cap stops seeking the
// player.
const maxTimesSeekAllow = 20;
if (player.current && roomRunning?.running && roomRunning.readyToParticipate) {
let targetTime = roomTime() / 1000;
let diff = player.current.currentTime - targetTime;
console.log('PLAYER DIFF', 'Our time diff with the server: ', diff,
'Time required to seek (seconds): ', diffSecondsRequiredToSeekRunningPlayer);
if (Math.abs(diff) > diffSecondsRequiredToSeekRunningPlayer) {
if (roomRunning.speedFactor != 1 || timesSeeked < maxTimesSeekAllow) {
player.current.currentTime = targetTime;
player.current.playbackRate = Math.max(Math.min(4.0, roomRunning.speedFactor), 0.25);
setTimesSeeked(timesSeeked + 1);
console.log('SEEKED', 'The running player time was seeked, times seeked in total: ' + timesSeeked);
} else {
console.warn('The running player is off, but we\'ve changed the time ' +
'too often, skipping synchronizing the player.');
}
}
if (player.current.paused) {
player.current.play().catch(e => {
console.error('Error playing', e);
})
}
} else {
if (player.current) {
player.current.pause();
}
}
return null;
}
export default Player;

@ -0,0 +1,110 @@
import {roomTime, useRoomRunningAndReadyChanged, useRoomTime, useTimelineSongFileChanged} from "../lib/Connection";
import React, {createRef, SyntheticEvent, useRef, useState} from "react";
import '../css/player.sass'
import {Room} from "../types/types";
import {parse as parseQueryString} from "query-string";
const Player = () => {
const room = useRoomRunningAndReadyChanged();
const _ = useRoomTime()
const timeline = useTimelineSongFileChanged();
let player = useRef<HTMLAudioElement>(null)
const [timesSeeked, setTimesSeeked] = useState(0);
const [hadError, setHadError] = useState(false);
// If our time synchronisation algorithm thing thinks the time is off by more
// than this value, we seek the running player to correct it.
const diffSecondsRequiredToSeekRunningPlayer = 0.20;
// Hard cap we are allowed to seek this player. Some browsers are slow or inaccurate
// and will always be off. To avoid endless skipping of the song this cap stops seeking the
// player.
const maxTimesSeekAllow = 25;
const query = parseQueryString(window.location.search);
if (query.nosound) {
return null;
}
if (player.current && player.current.dataset.src != timeline!!.songFile) {
player.current.dataset.src = timeline!!.songFile;
player.current.src = timeline!!.songFile;
}
function handlePlayerOnPlay(e: SyntheticEvent) {
e.preventDefault();
// For when the user manually started the player for when autoplay is off.
setHadError(false);
if (shouldPlay()) {
startPlaying(true)
}
}
function shouldPlay() {
return player.current && timeline && room && room.running && room.readyToParticipate
}
function startPlaying(manual: boolean) {
if (!player.current) return;
if (player.current.paused && !hadError) {
player.current.play().then(() => {
setHadError(false);
}).catch(e => {
console.error('Error playing', e);
setHadError(true);
})
}
if (!hadError) {
setPlayerTime(room!!, manual);
}
}
function setPlayerTime(room: Room, manualAdjustment: boolean) {
if (!player.current) return;
let targetTime = roomTime() / 1000;
let diff = player.current.currentTime - targetTime;
console.log('PLAYER DIFF', diff,
'min req to seek: ', diffSecondsRequiredToSeekRunningPlayer);
if (player.current && Math.abs(diff) > diffSecondsRequiredToSeekRunningPlayer) {
if (room.speedFactor != 1 || manualAdjustment || timesSeeked < maxTimesSeekAllow) {
player.current.currentTime = targetTime;
player.current.playbackRate = Math.max(Math.min(4.0, room.speedFactor), 0.25);
if (!manualAdjustment) {
setTimesSeeked(timesSeeked + 1);
}
console.log('PLAYER SEEKED', 'Player was seeked (total: ' + timesSeeked + ')');
} else {
console.warn('The running player is off, but we\'ve changed the time ' +
'too often, skipping synchronizing the player.');
}
}
}
if (shouldPlay()) {
startPlaying(false)
} else {
if (player.current) {
player.current.pause();
}
}
function render() {
return (
<audio ref={player} className='player' hidden={!hadError} controls={true} onPlay={handlePlayerOnPlay}/>
)
}
return render();
}
export default Player;

@ -0,0 +1,4 @@
.player
position: fixed
left: 0
bottom: 0

@ -87,6 +87,7 @@ class Connection {
} else { } else {
call.callback(null, data.response); call.callback(null, data.response);
} }
delete this.calls[data.id];
}); });
} }
@ -110,6 +111,11 @@ class Connection {
this.stopTimeSync(); this.stopTimeSync();
} }
autoStart() : boolean {
let query = parseQueryString(window.location.search);
return !!query.autostart;
}
private getQueryLobbyId(): number | null { private getQueryLobbyId(): number | null {
let query = parseQueryString(window.location.search); let query = parseQueryString(window.location.search);
if (query.lobby) { if (query.lobby) {
@ -162,7 +168,10 @@ class Connection {
async requestJoin(roomId: number): Promise<boolean> { async requestJoin(roomId: number): Promise<boolean> {
return this.call('room_exists', {roomId: roomId}).then(v => { return this.call('room_exists', {roomId: roomId}).then(v => {
if (v) { if (v) {
this.socket.emit('request_join', roomId); this.socket.emit('request_join', roomId, this.autoStart());
if (this.autoStart()) {
this.requestReady();
}
return true; return true;
} else { } else {
return false; return false;
@ -208,7 +217,8 @@ class Connection {
timeSyncResponse(requestId: number, clientDiff: number, serverTime: number) { timeSyncResponse(requestId: number, clientDiff: number, serverTime: number) {
let syncReq = this.timeSyncs[requestId]; let syncReq = this.timeSyncs[requestId];
if (!syncReq) return; if (!syncReq) return
delete this.timeSyncs[requestId];
syncReq.response(clientDiff, serverTime); syncReq.response(clientDiff, serverTime);
for (let i in this.timeSyncs) { for (let i in this.timeSyncs) {

Loading…
Cancel
Save