Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Commit

Permalink
Add voice broadcast playback pip (#9603)
Browse files Browse the repository at this point in the history
  • Loading branch information
weeman1337 authored and Amy Walker committed Nov 28, 2022
1 parent eacaa34 commit cf7d9e3
Show file tree
Hide file tree
Showing 13 changed files with 372 additions and 41 deletions.
23 changes: 22 additions & 1 deletion src/components/views/voip/PipView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,14 @@ import { CallStore } from "../../../stores/CallStore";
import {
useCurrentVoiceBroadcastPreRecording,
useCurrentVoiceBroadcastRecording,
VoiceBroadcastPlayback,
VoiceBroadcastPlaybackBody,
VoiceBroadcastPreRecording,
VoiceBroadcastPreRecordingPip,
VoiceBroadcastRecording,
VoiceBroadcastRecordingPip,
} from '../../../voice-broadcast';
import { useCurrentVoiceBroadcastPlayback } from '../../../voice-broadcast/hooks/useCurrentVoiceBroadcastPlayback';

const SHOW_CALL_IN_STATES = [
CallState.Connected,
Expand All @@ -57,6 +60,7 @@ const SHOW_CALL_IN_STATES = [
interface IProps {
voiceBroadcastRecording?: Optional<VoiceBroadcastRecording>;
voiceBroadcastPreRecording?: Optional<VoiceBroadcastPreRecording>;
voiceBroadcastPlayback?: Optional<VoiceBroadcastPlayback>;
}

interface IState {
Expand Down Expand Up @@ -330,6 +334,15 @@ class PipView extends React.Component<IProps, IState> {
this.setState({ showWidgetInPip, persistentWidgetId, persistentRoomId });
}

private createVoiceBroadcastPlaybackPipContent(voiceBroadcastPlayback: VoiceBroadcastPlayback): CreatePipChildren {
return ({ onStartMoving }) => <div onMouseDown={onStartMoving}>
<VoiceBroadcastPlaybackBody
playback={voiceBroadcastPlayback}
pip={true}
/>
</div>;
}

private createVoiceBroadcastPreRecordingPipContent(
voiceBroadcastPreRecording: VoiceBroadcastPreRecording,
): CreatePipChildren {
Expand Down Expand Up @@ -358,6 +371,10 @@ class PipView extends React.Component<IProps, IState> {
pipContent = this.createVoiceBroadcastPreRecordingPipContent(this.props.voiceBroadcastPreRecording);
}

if (this.props.voiceBroadcastPlayback) {
pipContent = this.createVoiceBroadcastPlaybackPipContent(this.props.voiceBroadcastPlayback);
}

if (this.props.voiceBroadcastRecording) {
pipContent = this.createVoiceBroadcastRecordingPipContent(this.props.voiceBroadcastRecording);
}
Expand Down Expand Up @@ -430,9 +447,13 @@ const PipViewHOC: React.FC<IProps> = (props) => {
const voiceBroadcastRecordingsStore = sdkContext.voiceBroadcastRecordingsStore;
const { currentVoiceBroadcastRecording } = useCurrentVoiceBroadcastRecording(voiceBroadcastRecordingsStore);

const voiceBroadcastPlaybacksStore = sdkContext.voiceBroadcastPlaybacksStore;
const { currentVoiceBroadcastPlayback } = useCurrentVoiceBroadcastPlayback(voiceBroadcastPlaybacksStore);

return <PipView
voiceBroadcastRecording={currentVoiceBroadcastRecording}
voiceBroadcastPlayback={currentVoiceBroadcastPlayback}
voiceBroadcastPreRecording={currentVoiceBroadcastPreRecording}
voiceBroadcastRecording={currentVoiceBroadcastRecording}
{...props}
/>;
};
Expand Down
14 changes: 13 additions & 1 deletion src/contexts/SDKContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,11 @@ import TypingStore from "../stores/TypingStore";
import { WidgetLayoutStore } from "../stores/widgets/WidgetLayoutStore";
import { WidgetPermissionStore } from "../stores/widgets/WidgetPermissionStore";
import WidgetStore from "../stores/WidgetStore";
import { VoiceBroadcastPreRecordingStore, VoiceBroadcastRecordingsStore } from "../voice-broadcast";
import {
VoiceBroadcastPlaybacksStore,
VoiceBroadcastPreRecordingStore,
VoiceBroadcastRecordingsStore,
} from "../voice-broadcast";

export const SDKContext = createContext<SdkContextClass>(undefined);
SDKContext.displayName = "SDKContext";
Expand Down Expand Up @@ -68,6 +72,7 @@ export class SdkContextClass {
protected _TypingStore?: TypingStore;
protected _VoiceBroadcastRecordingsStore?: VoiceBroadcastRecordingsStore;
protected _VoiceBroadcastPreRecordingStore?: VoiceBroadcastPreRecordingStore;
protected _VoiceBroadcastPlaybacksStore?: VoiceBroadcastPlaybacksStore;

/**
* Automatically construct stores which need to be created eagerly so they can register with
Expand Down Expand Up @@ -166,4 +171,11 @@ export class SdkContextClass {
}
return this._VoiceBroadcastPreRecordingStore;
}

public get voiceBroadcastPlaybacksStore(): VoiceBroadcastPlaybacksStore {
if (!this._VoiceBroadcastPlaybacksStore) {
this._VoiceBroadcastPlaybacksStore = VoiceBroadcastPlaybacksStore.instance();
}
return this._VoiceBroadcastPlaybacksStore;
}
}
35 changes: 35 additions & 0 deletions src/stores/RoomViewStore.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ import { UPDATE_EVENT } from "./AsyncStore";
import { SdkContextClass } from "../contexts/SDKContext";
import { CallStore } from "./CallStore";
import { ThreadPayload } from "../dispatcher/payloads/ThreadPayload";
import {
doClearCurrentVoiceBroadcastPlaybackIfStopped,
doMaybeSetCurrentVoiceBroadcastPlayback,
} from "../voice-broadcast";
import { IRoomStateEventsActionPayload } from "../actions/MatrixActionCreators";

const NUM_JOIN_RETRY = 5;

Expand Down Expand Up @@ -195,6 +200,28 @@ export class RoomViewStore extends EventEmitter {
this.emit(UPDATE_EVENT);
}

private doMaybeSetCurrentVoiceBroadcastPlayback(room: Room): void {
doMaybeSetCurrentVoiceBroadcastPlayback(
room,
this.stores.client,
this.stores.voiceBroadcastPlaybacksStore,
this.stores.voiceBroadcastRecordingsStore,
);
}

private onRoomStateEvents(event: MatrixEvent): void {
const roomId = event.getRoomId?.();

// no room or not current room
if (!roomId || roomId !== this.state.roomId) return;

const room = this.stores.client?.getRoom(roomId);

if (room) {
this.doMaybeSetCurrentVoiceBroadcastPlayback(room);
}
}

private onDispatch(payload): void { // eslint-disable-line @typescript-eslint/naming-convention
switch (payload.action) {
// view_room:
Expand All @@ -219,6 +246,10 @@ export class RoomViewStore extends EventEmitter {
wasContextSwitch: false,
viewingCall: false,
});
doClearCurrentVoiceBroadcastPlaybackIfStopped(this.stores.voiceBroadcastPlaybacksStore);
break;
case "MatrixActions.RoomState.events":
this.onRoomStateEvents((payload as IRoomStateEventsActionPayload).event);
break;
case Action.ViewRoomError:
this.viewRoomError(payload);
Expand Down Expand Up @@ -395,6 +426,10 @@ export class RoomViewStore extends EventEmitter {
metricsTrigger: payload.metricsTrigger as JoinRoomPayload["metricsTrigger"],
});
}

if (room) {
this.doMaybeSetCurrentVoiceBroadcastPlayback(room);
}
} else if (payload.room_alias) {
// Try the room alias to room ID navigation cache first to avoid
// blocking room navigation on the homeserver.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ limitations under the License.
*/

import React, { ReactElement } from "react";
import classNames from "classnames";

import {
VoiceBroadcastControl,
Expand All @@ -36,10 +37,12 @@ import { SeekButton } from "../atoms/SeekButton";
const SEEK_TIME = 30;

interface VoiceBroadcastPlaybackBodyProps {
pip?: boolean;
playback: VoiceBroadcastPlayback;
}

export const VoiceBroadcastPlaybackBody: React.FC<VoiceBroadcastPlaybackBodyProps> = ({
pip = false,
playback,
}) => {
const {
Expand Down Expand Up @@ -107,8 +110,13 @@ export const VoiceBroadcastPlaybackBody: React.FC<VoiceBroadcastPlaybackBodyProp
/>;
}

const classes = classNames({
mx_VoiceBroadcastBody: true,
["mx_VoiceBroadcastBody--pip"]: pip,
});

return (
<div className="mx_VoiceBroadcastBody">
<div className={classes}>
<VoiceBroadcastHeader
live={liveness}
microphoneLabel={sender?.name}
Expand Down
44 changes: 44 additions & 0 deletions src/voice-broadcast/hooks/useCurrentVoiceBroadcastPlayback.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
Copyright 2022 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import { useState } from "react";

import { useTypedEventEmitter } from "../../hooks/useEventEmitter";
import { VoiceBroadcastPlayback } from "../models/VoiceBroadcastPlayback";
import {
VoiceBroadcastPlaybacksStore,
VoiceBroadcastPlaybacksStoreEvent,
} from "../stores/VoiceBroadcastPlaybacksStore";

export const useCurrentVoiceBroadcastPlayback = (
voiceBroadcastPlaybackStore: VoiceBroadcastPlaybacksStore,
) => {
const [currentVoiceBroadcastPlayback, setVoiceBroadcastPlayback] = useState(
voiceBroadcastPlaybackStore.getCurrent(),
);

useTypedEventEmitter(
voiceBroadcastPlaybackStore,
VoiceBroadcastPlaybacksStoreEvent.CurrentChanged,
(playback: VoiceBroadcastPlayback) => {
setVoiceBroadcastPlayback(playback);
},
);

return {
currentVoiceBroadcastPlayback,
};
};
2 changes: 2 additions & 0 deletions src/voice-broadcast/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ export * from "./stores/VoiceBroadcastPlaybacksStore";
export * from "./stores/VoiceBroadcastPreRecordingStore";
export * from "./stores/VoiceBroadcastRecordingsStore";
export * from "./utils/checkVoiceBroadcastPreConditions";
export * from "./utils/doClearCurrentVoiceBroadcastPlaybackIfStopped";
export * from "./utils/doMaybeSetCurrentVoiceBroadcastPlayback";
export * from "./utils/getChunkLength";
export * from "./utils/getMaxBroadcastLength";
export * from "./utils/hasRoomLiveVoiceBroadcast";
Expand Down
25 changes: 18 additions & 7 deletions src/voice-broadcast/stores/VoiceBroadcastPlaybacksStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export enum VoiceBroadcastPlaybacksStoreEvent {
}

interface EventMap {
[VoiceBroadcastPlaybacksStoreEvent.CurrentChanged]: (recording: VoiceBroadcastPlayback) => void;
[VoiceBroadcastPlaybacksStoreEvent.CurrentChanged]: (recording: VoiceBroadcastPlayback | null) => void;
}

/**
Expand Down Expand Up @@ -53,7 +53,14 @@ export class VoiceBroadcastPlaybacksStore
this.emit(VoiceBroadcastPlaybacksStoreEvent.CurrentChanged, current);
}

public getCurrent(): VoiceBroadcastPlayback {
public clearCurrent(): void {
if (this.current === null) return;

this.current = null;
this.emit(VoiceBroadcastPlaybacksStoreEvent.CurrentChanged, null);
}

public getCurrent(): VoiceBroadcastPlayback | null {
return this.current;
}

Expand All @@ -80,11 +87,15 @@ export class VoiceBroadcastPlaybacksStore
state: VoiceBroadcastPlaybackState,
playback: VoiceBroadcastPlayback,
): void => {
if ([
VoiceBroadcastPlaybackState.Buffering,
VoiceBroadcastPlaybackState.Playing,
].includes(state)) {
this.pauseExcept(playback);
switch (state) {
case VoiceBroadcastPlaybackState.Buffering:
case VoiceBroadcastPlaybackState.Playing:
this.pauseExcept(playback);
this.setCurrent(playback);
break;
case VoiceBroadcastPlaybackState.Stopped:
this.clearCurrent();
break;
}
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
Copyright 2022 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import { VoiceBroadcastPlaybacksStore, VoiceBroadcastPlaybackState } from "..";

export const doClearCurrentVoiceBroadcastPlaybackIfStopped = (
voiceBroadcastPlaybacksStore: VoiceBroadcastPlaybacksStore,
) => {
if (voiceBroadcastPlaybacksStore.getCurrent()?.getState() === VoiceBroadcastPlaybackState.Stopped) {
// clear current if stopped
return;
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
Copyright 2022 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import { MatrixClient, Room } from "matrix-js-sdk/src/matrix";

import {
hasRoomLiveVoiceBroadcast,
VoiceBroadcastPlaybacksStore,
VoiceBroadcastPlaybackState,
VoiceBroadcastRecordingsStore,
} from "..";

/**
* When a live voice broadcast is in the room and
* another voice broadcast is not currently being listened to or recorded
* the live broadcast in the room is set as the current broadcast to listen to.
* When there is no live broadcast in the room: clear current broadcast.
*
* @param {Room} room The room to check for a live voice broadcast
* @param {MatrixClient} client
* @param {VoiceBroadcastPlaybacksStore} voiceBroadcastPlaybacksStore
* @param {VoiceBroadcastRecordingsStore} voiceBroadcastRecordingsStore
*/
export const doMaybeSetCurrentVoiceBroadcastPlayback = (
room: Room,
client: MatrixClient,
voiceBroadcastPlaybacksStore: VoiceBroadcastPlaybacksStore,
voiceBroadcastRecordingsStore: VoiceBroadcastRecordingsStore,
): void => {
// do not disturb the current recording
if (voiceBroadcastRecordingsStore.hasCurrent()) return;

const currentPlayback = voiceBroadcastPlaybacksStore.getCurrent();

if (currentPlayback && currentPlayback.getState() !== VoiceBroadcastPlaybackState.Stopped) {
// do not disturb the current playback
return;
}

const { infoEvent } = hasRoomLiveVoiceBroadcast(room);

if (infoEvent) {
// live broadcast in the room + no recording + not listening yet: set the current broadcast
const voiceBroadcastPlayback = voiceBroadcastPlaybacksStore.getByInfoEvent(infoEvent, client);
voiceBroadcastPlaybacksStore.setCurrent(voiceBroadcastPlayback);
return;
}

// no broadcast; not listening: clear current
voiceBroadcastPlaybacksStore.clearCurrent();
};
Loading

0 comments on commit cf7d9e3

Please sign in to comment.