Show time left for voice broadcast recordings (#9564)
This commit is contained in:
parent
962e8e0b23
commit
f6347d24ef
22 changed files with 469 additions and 145 deletions
|
@ -149,6 +149,31 @@ export function formatSeconds(inSeconds: number): string {
|
|||
return output;
|
||||
}
|
||||
|
||||
export function formatTimeLeft(inSeconds: number): string {
|
||||
const hours = Math.floor(inSeconds / (60 * 60)).toFixed(0);
|
||||
const minutes = Math.floor((inSeconds % (60 * 60)) / 60).toFixed(0);
|
||||
const seconds = Math.floor(((inSeconds % (60 * 60)) % 60)).toFixed(0);
|
||||
|
||||
if (hours !== "0") {
|
||||
return _t("%(hours)sh %(minutes)sm %(seconds)ss left", {
|
||||
hours,
|
||||
minutes,
|
||||
seconds,
|
||||
});
|
||||
}
|
||||
|
||||
if (minutes !== "0") {
|
||||
return _t("%(minutes)sm %(seconds)ss left", {
|
||||
minutes,
|
||||
seconds,
|
||||
});
|
||||
}
|
||||
|
||||
return _t("%(seconds)ss left", {
|
||||
seconds,
|
||||
});
|
||||
}
|
||||
|
||||
const MILLIS_IN_DAY = 86400000;
|
||||
function withinPast24Hours(prevDate: Date, nextDate: Date): boolean {
|
||||
return Math.abs(prevDate.getTime() - nextDate.getTime()) <= MILLIS_IN_DAY;
|
||||
|
|
|
@ -182,6 +182,8 @@ export interface IConfigOptions {
|
|||
voice_broadcast?: {
|
||||
// length per voice chunk in seconds
|
||||
chunk_length?: number;
|
||||
// max voice broadcast length in seconds
|
||||
max_length?: number;
|
||||
};
|
||||
|
||||
user_notice?: {
|
||||
|
|
|
@ -47,7 +47,8 @@ export const DEFAULTS: IConfigOptions = {
|
|||
url: "https://element.io/get-started",
|
||||
},
|
||||
voice_broadcast: {
|
||||
chunk_length: 120, // two minutes
|
||||
chunk_length: 2 * 60, // two minutes
|
||||
max_length: 4 * 60 * 60, // four hours
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -18,20 +18,26 @@ import React, { HTMLProps } from "react";
|
|||
|
||||
import { formatSeconds } from "../../../DateUtils";
|
||||
|
||||
interface IProps extends Pick<HTMLProps<HTMLSpanElement>, "aria-live" | "role"> {
|
||||
interface Props extends Pick<HTMLProps<HTMLSpanElement>, "aria-live" | "role"> {
|
||||
seconds: number;
|
||||
formatFn?: (seconds: number) => string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simply converts seconds into minutes and seconds. Note that hours will not be
|
||||
* displayed, making it possible to see "82:29".
|
||||
* Simply converts seconds using formatFn.
|
||||
* Defaulting to formatSeconds().
|
||||
* Note that in this case hours will not be displayed, making it possible to see "82:29".
|
||||
*/
|
||||
export default class Clock extends React.Component<IProps> {
|
||||
public constructor(props) {
|
||||
export default class Clock extends React.Component<Props> {
|
||||
public static defaultProps = {
|
||||
formatFn: formatSeconds,
|
||||
};
|
||||
|
||||
public constructor(props: Props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
public shouldComponentUpdate(nextProps: Readonly<IProps>): boolean {
|
||||
public shouldComponentUpdate(nextProps: Readonly<Props>): boolean {
|
||||
const currentFloor = Math.floor(this.props.seconds);
|
||||
const nextFloor = Math.floor(nextProps.seconds);
|
||||
return currentFloor !== nextFloor;
|
||||
|
@ -39,7 +45,7 @@ export default class Clock extends React.Component<IProps> {
|
|||
|
||||
public render() {
|
||||
return <span aria-live={this.props["aria-live"]} role={this.props.role} className='mx_Clock'>
|
||||
{ formatSeconds(this.props.seconds) }
|
||||
{ this.props.formatFn(this.props.seconds) }
|
||||
</span>;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,6 +48,9 @@
|
|||
"%(weekDayName)s, %(monthName)s %(day)s %(time)s": "%(weekDayName)s, %(monthName)s %(day)s %(time)s",
|
||||
"%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s": "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s",
|
||||
"%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s": "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s",
|
||||
"%(hours)sh %(minutes)sm %(seconds)ss left": "%(hours)sh %(minutes)sm %(seconds)ss left",
|
||||
"%(minutes)sm %(seconds)ss left": "%(minutes)sm %(seconds)ss left",
|
||||
"%(seconds)ss left": "%(seconds)ss left",
|
||||
"%(date)s at %(time)s": "%(date)s at %(time)s",
|
||||
"%(value)sd": "%(value)sd",
|
||||
"%(value)sh": "%(value)sh",
|
||||
|
@ -1886,7 +1889,6 @@
|
|||
"The conversation continues here.": "The conversation continues here.",
|
||||
"This room has been replaced and is no longer active.": "This room has been replaced and is no longer active.",
|
||||
"You do not have permission to post to this room": "You do not have permission to post to this room",
|
||||
"%(seconds)ss left": "%(seconds)ss left",
|
||||
"Send voice message": "Send voice message",
|
||||
"Hide stickers": "Hide stickers",
|
||||
"Sticker": "Sticker",
|
||||
|
|
|
@ -18,17 +18,19 @@ import { Optional } from "matrix-events-sdk";
|
|||
import { TypedEventEmitter } from "matrix-js-sdk/src/models/typed-event-emitter";
|
||||
|
||||
import { getChunkLength } from "..";
|
||||
import { VoiceRecording } from "../../audio/VoiceRecording";
|
||||
import { IRecordingUpdate, VoiceRecording } from "../../audio/VoiceRecording";
|
||||
import { concat } from "../../utils/arrays";
|
||||
import { IDestroyable } from "../../utils/IDestroyable";
|
||||
import { Singleflight } from "../../utils/Singleflight";
|
||||
|
||||
export enum VoiceBroadcastRecorderEvent {
|
||||
ChunkRecorded = "chunk_recorded",
|
||||
CurrentChunkLengthUpdated = "current_chunk_length_updated",
|
||||
}
|
||||
|
||||
interface EventMap {
|
||||
[VoiceBroadcastRecorderEvent.ChunkRecorded]: (chunk: ChunkRecordedPayload) => void;
|
||||
[VoiceBroadcastRecorderEvent.CurrentChunkLengthUpdated]: (length: number) => void;
|
||||
}
|
||||
|
||||
export interface ChunkRecordedPayload {
|
||||
|
@ -46,8 +48,11 @@ export class VoiceBroadcastRecorder
|
|||
implements IDestroyable {
|
||||
private headers = new Uint8Array(0);
|
||||
private chunkBuffer = new Uint8Array(0);
|
||||
// position of the previous chunk in seconds
|
||||
private previousChunkEndTimePosition = 0;
|
||||
private pagesFromRecorderCount = 0;
|
||||
// current chunk length in seconds
|
||||
private currentChunkLength = 0;
|
||||
|
||||
public constructor(
|
||||
private voiceRecording: VoiceRecording,
|
||||
|
@ -58,7 +63,11 @@ export class VoiceBroadcastRecorder
|
|||
}
|
||||
|
||||
public async start(): Promise<void> {
|
||||
return this.voiceRecording.start();
|
||||
await this.voiceRecording.start();
|
||||
this.voiceRecording.liveData.onUpdate((data: IRecordingUpdate) => {
|
||||
this.setCurrentChunkLength(data.timeSeconds - this.previousChunkEndTimePosition);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -68,15 +77,25 @@ export class VoiceBroadcastRecorder
|
|||
await this.voiceRecording.stop();
|
||||
// forget about that call, so that we can stop it again later
|
||||
Singleflight.forgetAllFor(this.voiceRecording);
|
||||
return this.extractChunk();
|
||||
const chunk = this.extractChunk();
|
||||
this.currentChunkLength = 0;
|
||||
this.previousChunkEndTimePosition = 0;
|
||||
return chunk;
|
||||
}
|
||||
|
||||
public get contentType(): string {
|
||||
return this.voiceRecording.contentType;
|
||||
}
|
||||
|
||||
private get chunkLength(): number {
|
||||
return this.voiceRecording.recorderSeconds - this.previousChunkEndTimePosition;
|
||||
private setCurrentChunkLength(currentChunkLength: number): void {
|
||||
if (this.currentChunkLength === currentChunkLength) return;
|
||||
|
||||
this.currentChunkLength = currentChunkLength;
|
||||
this.emit(VoiceBroadcastRecorderEvent.CurrentChunkLengthUpdated, currentChunkLength);
|
||||
}
|
||||
|
||||
public getCurrentChunkLength(): number {
|
||||
return this.currentChunkLength;
|
||||
}
|
||||
|
||||
private onDataAvailable = (data: ArrayBuffer): void => {
|
||||
|
@ -89,6 +108,7 @@ export class VoiceBroadcastRecorder
|
|||
return;
|
||||
}
|
||||
|
||||
this.setCurrentChunkLength(this.voiceRecording.recorderSeconds - this.previousChunkEndTimePosition);
|
||||
this.handleData(dataArray);
|
||||
};
|
||||
|
||||
|
@ -98,7 +118,7 @@ export class VoiceBroadcastRecorder
|
|||
}
|
||||
|
||||
private emitChunkIfTargetLengthReached(): void {
|
||||
if (this.chunkLength >= this.targetChunkLength) {
|
||||
if (this.getCurrentChunkLength() >= this.targetChunkLength) {
|
||||
this.emitAndResetChunk();
|
||||
}
|
||||
}
|
||||
|
@ -114,9 +134,10 @@ export class VoiceBroadcastRecorder
|
|||
const currentRecorderTime = this.voiceRecording.recorderSeconds;
|
||||
const payload: ChunkRecordedPayload = {
|
||||
buffer: concat(this.headers, this.chunkBuffer),
|
||||
length: this.chunkLength,
|
||||
length: this.getCurrentChunkLength(),
|
||||
};
|
||||
this.chunkBuffer = new Uint8Array(0);
|
||||
this.setCurrentChunkLength(0);
|
||||
this.previousChunkEndTimePosition = currentRecorderTime;
|
||||
return payload;
|
||||
}
|
||||
|
|
|
@ -17,10 +17,13 @@ import { Room, RoomMember } from "matrix-js-sdk/src/matrix";
|
|||
import { LiveBadge } from "../..";
|
||||
import { Icon as LiveIcon } from "../../../../res/img/element-icons/live.svg";
|
||||
import { Icon as MicrophoneIcon } from "../../../../res/img/voip/call-view/mic-on.svg";
|
||||
import { Icon as TimerIcon } from "../../../../res/img/element-icons/Timer.svg";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import RoomAvatar from "../../../components/views/avatars/RoomAvatar";
|
||||
import AccessibleButton from "../../../components/views/elements/AccessibleButton";
|
||||
import { Icon as XIcon } from "../../../../res/img/element-icons/cancel-rounded.svg";
|
||||
import Clock from "../../../components/views/audio_messages/Clock";
|
||||
import { formatTimeLeft } from "../../../DateUtils";
|
||||
|
||||
interface VoiceBroadcastHeaderProps {
|
||||
live?: boolean;
|
||||
|
@ -28,6 +31,7 @@ interface VoiceBroadcastHeaderProps {
|
|||
room: Room;
|
||||
sender: RoomMember;
|
||||
showBroadcast?: boolean;
|
||||
timeLeft?: number;
|
||||
showClose?: boolean;
|
||||
}
|
||||
|
||||
|
@ -38,6 +42,7 @@ export const VoiceBroadcastHeader: React.FC<VoiceBroadcastHeaderProps> = ({
|
|||
sender,
|
||||
showBroadcast = false,
|
||||
showClose = false,
|
||||
timeLeft,
|
||||
}) => {
|
||||
const broadcast = showBroadcast
|
||||
? <div className="mx_VoiceBroadcastHeader_line">
|
||||
|
@ -54,6 +59,13 @@ export const VoiceBroadcastHeader: React.FC<VoiceBroadcastHeaderProps> = ({
|
|||
</AccessibleButton>
|
||||
: null;
|
||||
|
||||
const timeLeftLine = timeLeft
|
||||
? <div className="mx_VoiceBroadcastHeader_line">
|
||||
<TimerIcon className="mx_Icon mx_Icon_16" />
|
||||
<Clock formatFn={formatTimeLeft} seconds={timeLeft} />
|
||||
</div>
|
||||
: null;
|
||||
|
||||
return <div className="mx_VoiceBroadcastHeader">
|
||||
<RoomAvatar room={room} width={32} height={32} />
|
||||
<div className="mx_VoiceBroadcastHeader_content">
|
||||
|
@ -64,6 +76,7 @@ export const VoiceBroadcastHeader: React.FC<VoiceBroadcastHeaderProps> = ({
|
|||
<MicrophoneIcon className="mx_Icon mx_Icon_16" />
|
||||
<span>{ sender.name }</span>
|
||||
</div>
|
||||
{ timeLeftLine }
|
||||
{ broadcast }
|
||||
</div>
|
||||
{ liveBadge }
|
||||
|
|
|
@ -35,6 +35,7 @@ interface VoiceBroadcastRecordingPipProps {
|
|||
export const VoiceBroadcastRecordingPip: React.FC<VoiceBroadcastRecordingPipProps> = ({ recording }) => {
|
||||
const {
|
||||
live,
|
||||
timeLeft,
|
||||
recordingState,
|
||||
room,
|
||||
sender,
|
||||
|
@ -58,6 +59,7 @@ export const VoiceBroadcastRecordingPip: React.FC<VoiceBroadcastRecordingPipProp
|
|||
live={live}
|
||||
sender={sender}
|
||||
room={room}
|
||||
timeLeft={timeLeft}
|
||||
/>
|
||||
<hr className="mx_VoiceBroadcastBody_divider" />
|
||||
<div className="mx_VoiceBroadcastBody_controls">
|
||||
|
|
|
@ -65,6 +65,13 @@ export const useVoiceBroadcastRecording = (recording: VoiceBroadcastRecording) =
|
|||
},
|
||||
);
|
||||
|
||||
const [timeLeft, setTimeLeft] = useState(recording.getTimeLeft());
|
||||
useTypedEventEmitter(
|
||||
recording,
|
||||
VoiceBroadcastRecordingEvent.TimeLeftChanged,
|
||||
setTimeLeft,
|
||||
);
|
||||
|
||||
const live = [
|
||||
VoiceBroadcastInfoState.Started,
|
||||
VoiceBroadcastInfoState.Paused,
|
||||
|
@ -73,6 +80,7 @@ export const useVoiceBroadcastRecording = (recording: VoiceBroadcastRecording) =
|
|||
|
||||
return {
|
||||
live,
|
||||
timeLeft,
|
||||
recordingState,
|
||||
room,
|
||||
sender: recording.infoEvent.sender,
|
||||
|
|
|
@ -41,6 +41,7 @@ export * from "./stores/VoiceBroadcastPreRecordingStore";
|
|||
export * from "./stores/VoiceBroadcastRecordingsStore";
|
||||
export * from "./utils/checkVoiceBroadcastPreConditions";
|
||||
export * from "./utils/getChunkLength";
|
||||
export * from "./utils/getMaxBroadcastLength";
|
||||
export * from "./utils/hasRoomLiveVoiceBroadcast";
|
||||
export * from "./utils/findRoomLiveVoiceBroadcastFromUserAndDevice";
|
||||
export * from "./utils/shouldDisplayAsVoiceBroadcastRecordingTile";
|
||||
|
|
|
@ -15,12 +15,20 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { MatrixClient, MatrixEvent, MatrixEventEvent, RelationType } from "matrix-js-sdk/src/matrix";
|
||||
import {
|
||||
EventType,
|
||||
MatrixClient,
|
||||
MatrixEvent,
|
||||
MatrixEventEvent,
|
||||
MsgType,
|
||||
RelationType,
|
||||
} from "matrix-js-sdk/src/matrix";
|
||||
import { TypedEventEmitter } from "matrix-js-sdk/src/models/typed-event-emitter";
|
||||
|
||||
import {
|
||||
ChunkRecordedPayload,
|
||||
createVoiceBroadcastRecorder,
|
||||
getMaxBroadcastLength,
|
||||
VoiceBroadcastInfoEventContent,
|
||||
VoiceBroadcastInfoEventType,
|
||||
VoiceBroadcastInfoState,
|
||||
|
@ -33,13 +41,17 @@ import { createVoiceMessageContent } from "../../utils/createVoiceMessageContent
|
|||
import { IDestroyable } from "../../utils/IDestroyable";
|
||||
import dis from "../../dispatcher/dispatcher";
|
||||
import { ActionPayload } from "../../dispatcher/payloads";
|
||||
import { VoiceBroadcastChunkEvents } from "../utils/VoiceBroadcastChunkEvents";
|
||||
import { RelationsHelper, RelationsHelperEvent } from "../../events/RelationsHelper";
|
||||
|
||||
export enum VoiceBroadcastRecordingEvent {
|
||||
StateChanged = "liveness_changed",
|
||||
TimeLeftChanged = "time_left_changed",
|
||||
}
|
||||
|
||||
interface EventMap {
|
||||
[VoiceBroadcastRecordingEvent.StateChanged]: (state: VoiceBroadcastInfoState) => void;
|
||||
[VoiceBroadcastRecordingEvent.TimeLeftChanged]: (timeLeft: number) => void;
|
||||
}
|
||||
|
||||
export class VoiceBroadcastRecording
|
||||
|
@ -49,6 +61,10 @@ export class VoiceBroadcastRecording
|
|||
private recorder: VoiceBroadcastRecorder;
|
||||
private sequence = 1;
|
||||
private dispatcherRef: string;
|
||||
private chunkEvents = new VoiceBroadcastChunkEvents();
|
||||
private chunkRelationHelper: RelationsHelper;
|
||||
private maxLength: number;
|
||||
private timeLeft: number;
|
||||
|
||||
public constructor(
|
||||
public readonly infoEvent: MatrixEvent,
|
||||
|
@ -56,6 +72,8 @@ export class VoiceBroadcastRecording
|
|||
initialState?: VoiceBroadcastInfoState,
|
||||
) {
|
||||
super();
|
||||
this.maxLength = getMaxBroadcastLength();
|
||||
this.timeLeft = this.maxLength;
|
||||
|
||||
if (initialState) {
|
||||
this.state = initialState;
|
||||
|
@ -64,11 +82,41 @@ export class VoiceBroadcastRecording
|
|||
}
|
||||
|
||||
// TODO Michael W: listen for state updates
|
||||
//
|
||||
|
||||
this.infoEvent.on(MatrixEventEvent.BeforeRedaction, this.onBeforeRedaction);
|
||||
this.dispatcherRef = dis.register(this.onAction);
|
||||
this.chunkRelationHelper = this.initialiseChunkEventRelation();
|
||||
}
|
||||
|
||||
private initialiseChunkEventRelation(): RelationsHelper {
|
||||
const relationsHelper = new RelationsHelper(
|
||||
this.infoEvent,
|
||||
RelationType.Reference,
|
||||
EventType.RoomMessage,
|
||||
this.client,
|
||||
);
|
||||
relationsHelper.on(RelationsHelperEvent.Add, this.onChunkEvent);
|
||||
|
||||
relationsHelper.emitFetchCurrent().catch((err) => {
|
||||
logger.warn("error fetching server side relation for voice broadcast chunks", err);
|
||||
// fall back to local events
|
||||
relationsHelper.emitCurrent();
|
||||
});
|
||||
|
||||
return relationsHelper;
|
||||
}
|
||||
|
||||
private onChunkEvent = (event: MatrixEvent): void => {
|
||||
if (
|
||||
(!event.getId() && !event.getTxnId())
|
||||
|| event.getContent()?.msgtype !== MsgType.Audio // don't add non-audio event
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.chunkEvents.addEvent(event);
|
||||
};
|
||||
|
||||
private setInitialStateFromInfoEvent(): void {
|
||||
const room = this.client.getRoom(this.infoEvent.getRoomId());
|
||||
const relations = room?.getUnfilteredTimelineSet()?.relations?.getChildEventsForEvent(
|
||||
|
@ -82,6 +130,23 @@ export class VoiceBroadcastRecording
|
|||
}) ? VoiceBroadcastInfoState.Started : VoiceBroadcastInfoState.Stopped;
|
||||
}
|
||||
|
||||
public getTimeLeft(): number {
|
||||
return this.timeLeft;
|
||||
}
|
||||
|
||||
private async setTimeLeft(timeLeft: number): Promise<void> {
|
||||
if (timeLeft <= 0) {
|
||||
// time is up - stop the recording
|
||||
return await this.stop();
|
||||
}
|
||||
|
||||
// do never increase time left; no action if equals
|
||||
if (timeLeft >= this.timeLeft) return;
|
||||
|
||||
this.timeLeft = timeLeft;
|
||||
this.emit(VoiceBroadcastRecordingEvent.TimeLeftChanged, timeLeft);
|
||||
}
|
||||
|
||||
public async start(): Promise<void> {
|
||||
return this.getRecorder().start();
|
||||
}
|
||||
|
@ -127,20 +192,23 @@ export class VoiceBroadcastRecording
|
|||
if (!this.recorder) {
|
||||
this.recorder = createVoiceBroadcastRecorder();
|
||||
this.recorder.on(VoiceBroadcastRecorderEvent.ChunkRecorded, this.onChunkRecorded);
|
||||
this.recorder.on(VoiceBroadcastRecorderEvent.CurrentChunkLengthUpdated, this.onCurrentChunkLengthUpdated);
|
||||
}
|
||||
|
||||
return this.recorder;
|
||||
}
|
||||
|
||||
public destroy(): void {
|
||||
public async destroy(): Promise<void> {
|
||||
if (this.recorder) {
|
||||
this.recorder.off(VoiceBroadcastRecorderEvent.ChunkRecorded, this.onChunkRecorded);
|
||||
this.recorder.stop();
|
||||
this.recorder.destroy();
|
||||
}
|
||||
|
||||
this.infoEvent.off(MatrixEventEvent.BeforeRedaction, this.onBeforeRedaction);
|
||||
this.removeAllListeners();
|
||||
dis.unregister(this.dispatcherRef);
|
||||
this.chunkEvents = new VoiceBroadcastChunkEvents();
|
||||
this.chunkRelationHelper.destroy();
|
||||
}
|
||||
|
||||
private onBeforeRedaction = () => {
|
||||
|
@ -163,6 +231,10 @@ export class VoiceBroadcastRecording
|
|||
this.emit(VoiceBroadcastRecordingEvent.StateChanged, this.state);
|
||||
}
|
||||
|
||||
private onCurrentChunkLengthUpdated = (currentChunkLength: number) => {
|
||||
this.setTimeLeft(this.maxLength - this.chunkEvents.getLengthSeconds() - currentChunkLength);
|
||||
};
|
||||
|
||||
private onChunkRecorded = async (chunk: ChunkRecordedPayload): Promise<void> => {
|
||||
const { url, file } = await this.uploadFile(chunk);
|
||||
await this.sendVoiceMessage(chunk, url, file);
|
||||
|
|
|
@ -62,6 +62,10 @@ export class VoiceBroadcastChunkEvents {
|
|||
}, 0);
|
||||
}
|
||||
|
||||
public getLengthSeconds(): number {
|
||||
return this.getLength() / 1000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the accumulated length to (excl.) a chunk event.
|
||||
*/
|
||||
|
|
29
src/voice-broadcast/utils/getMaxBroadcastLength.ts
Normal file
29
src/voice-broadcast/utils/getMaxBroadcastLength.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
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 SdkConfig, { DEFAULTS } from "../../SdkConfig";
|
||||
|
||||
/**
|
||||
* Returns the max length for voice broadcasts:
|
||||
* - Tries to get the value from the voice_broadcast.max_length config
|
||||
* - If that fails from DEFAULTS
|
||||
* - If that fails fall back to four hours
|
||||
*/
|
||||
export const getMaxBroadcastLength = (): number => {
|
||||
return SdkConfig.get("voice_broadcast")?.max_length
|
||||
|| DEFAULTS.voice_broadcast?.max_length
|
||||
|| 4 * 60 * 60;
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue