Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Playback APIs #297

Merged
merged 22 commits into from
Oct 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/little-pants-nail.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@signalwire/core': minor
'@signalwire/js': minor
'@signalwire/realtime-api': minor
---

Add support for the Playback APIs: `roomSession.play()` and the `RoomSessionPlayback` object to control it.
2 changes: 1 addition & 1 deletion packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export type {
} from './redux/interfaces'
export * as actions from './redux/actions'
export * as Rooms from './rooms'
export type { RoomSessionRecording } from './rooms'
export type { RoomSessionRecording, RoomSessionPlayback } from './rooms'
export const selectors = {
...sessionSelectors,
}
63 changes: 63 additions & 0 deletions packages/core/src/rooms/RoomSessionPlayback.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { configureJestStore } from '../testUtils'
import { EventEmitter } from '../utils/EventEmitter'
import {
createRoomSessionPlaybackObject,
RoomSessionPlayback,
RoomSessionPlaybackEventsHandlerMapping,
} from './RoomSessionPlayback'

describe('RoomSessionPlayback', () => {
describe('createRoomSessionPlaybackObject', () => {
let instance: RoomSessionPlayback
beforeEach(() => {
instance = createRoomSessionPlaybackObject({
store: configureJestStore(),
emitter: new EventEmitter<RoomSessionPlaybackEventsHandlerMapping>(),
})
// @ts-expect-error
instance.execute = jest.fn()
})

it('should control an active playback', async () => {
// Mock properties
instance.id = 'c22d7223-5a01-49fe-8da0-46bec8e75e32'
instance.roomSessionId = 'room-session-id'

const baseExecuteParams = {
method: '',
params: {
room_session_id: 'room-session-id',
playback_id: 'c22d7223-5a01-49fe-8da0-46bec8e75e32',
},
}
await instance.pause()
// @ts-expect-error
expect(instance.execute).toHaveBeenLastCalledWith({
...baseExecuteParams,
method: 'video.playback.pause',
})
await instance.resume()
// @ts-expect-error
expect(instance.execute).toHaveBeenLastCalledWith({
...baseExecuteParams,
method: 'video.playback.resume',
})
await instance.stop()
// @ts-expect-error
expect(instance.execute).toHaveBeenLastCalledWith({
...baseExecuteParams,
method: 'video.playback.stop',
})
await instance.setVolume(30)
// @ts-expect-error
expect(instance.execute).toHaveBeenLastCalledWith({
method: 'video.playback.set_volume',
params: {
room_session_id: 'room-session-id',
playback_id: 'c22d7223-5a01-49fe-8da0-46bec8e75e32',
volume: 30,
},
})
})
})
})
86 changes: 86 additions & 0 deletions packages/core/src/rooms/RoomSessionPlayback.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { connect } from '../redux'
import { BaseComponent } from '../BaseComponent'
import { BaseComponentOptions } from '../utils/interfaces'
import type {
VideoPlaybackContract,
VideoPlaybackMethods,
VideoPlaybackEventNames,
} from '../types/videoPlayback'

/**
* Instances of this class allow you to control (e.g., pause, resume, stop) the
* playback inside a room session. You can obtain instances of this class by
* starting a playback from the desired {@link RoomSession} (see
* {@link RoomSession.play})
*/
export interface RoomSessionPlayback extends VideoPlaybackContract {}

export type RoomSessionPlaybackEventsHandlerMapping = Record<
VideoPlaybackEventNames,
(playback: RoomSessionPlayback) => void
>

export class RoomSessionPlaybackAPI
extends BaseComponent<RoomSessionPlaybackEventsHandlerMapping>
implements VideoPlaybackMethods
{
async pause() {
await this.execute({
method: 'video.playback.pause',
params: {
room_session_id: this.getStateProperty('roomSessionId'),
playback_id: this.getStateProperty('id'),
},
})
}

async resume() {
await this.execute({
method: 'video.playback.resume',
params: {
room_session_id: this.getStateProperty('roomSessionId'),
playback_id: this.getStateProperty('id'),
},
})
}

async stop() {
await this.execute({
method: 'video.playback.stop',
params: {
room_session_id: this.getStateProperty('roomSessionId'),
playback_id: this.getStateProperty('id'),
},
})
}

async setVolume(volume: number) {
await this.execute({
method: 'video.playback.set_volume',
params: {
room_session_id: this.getStateProperty('roomSessionId'),
playback_id: this.getStateProperty('id'),
volume,
},
})
}
}

export const createRoomSessionPlaybackObject = (
params: BaseComponentOptions<RoomSessionPlaybackEventsHandlerMapping>
): RoomSessionPlayback => {
const playback = connect<
RoomSessionPlaybackEventsHandlerMapping,
RoomSessionPlaybackAPI,
RoomSessionPlayback
>({
store: params.store,
Component: RoomSessionPlaybackAPI,
componentListeners: {
errors: 'onError',
responses: 'onSuccess',
},
})(params)

return playback
}
1 change: 1 addition & 0 deletions packages/core/src/rooms/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ export interface BaseRoomInterface<

export * from './methods'
export * from './RoomSessionRecording'
export * from './RoomSessionPlayback'
55 changes: 52 additions & 3 deletions packages/core/src/rooms/methods.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { BaseRoomInterface } from '.'
import { VideoMemberEntity, VideoRecordingEntity } from '../types'
import { toLocalEvent } from '../utils'
import {
VideoMemberEntity,
VideoRecordingEntity,
VideoPlaybackEntity,
} from '../types'
import { toLocalEvent, toExternalJSON } from '../utils'
import { ExecuteExtendedOptions, RoomMethod } from '../utils/interfaces'

interface RoomMethodPropertyDescriptor<T, ParamsType>
Expand Down Expand Up @@ -129,7 +133,9 @@ export const setHideVideoMuted: RoomMethodDescriptor<any, boolean> = {
export const getRecordings = createRoomMethod<{
recordings: VideoRecordingEntity[]
}>('video.recording.list', {
transformResolve: (payload) => ({ recordings: payload.recordings }),
transformResolve: (payload) => ({
recordings: payload.recordings.map((row) => toExternalJSON(row)),
}),
Comment on lines +136 to +138
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note for @danieleds - we are transforming the properties from snake_case to camelCase here (and same for playbacks on L171 below)

})
export const startRecording: RoomMethodDescriptor<any> = {
value: function () {
Expand Down Expand Up @@ -158,6 +164,46 @@ export const startRecording: RoomMethodDescriptor<any> = {
},
}

export const getPlaybacks = createRoomMethod<{
playbacks: VideoPlaybackEntity[]
}>('video.playback.list', {
transformResolve: (payload) => ({
playbacks: payload.playbacks.map((row) => toExternalJSON(row)),
}),
})

export type PlayParams = {
url: string
volume?: number
}
export const play: RoomMethodDescriptor<any, PlayParams> = {
value: function (params) {
return new Promise(async (resolve) => {
const handler = (instance: any) => {
resolve(instance)
}
this.on(toLocalEvent('video.playback.start'), handler)

try {
const payload = await this.execute({
method: 'video.playback.start',
params: {
room_session_id: this.roomSessionId,
...params,
},
})
this.emit(toLocalEvent('video.playback.start'), {
...(payload as object),
room_session_id: this.roomSessionId,
})
} catch (error) {
this.off(toLocalEvent('video.playback.start'), handler)
throw error
}
})
},
}

export type GetLayouts = ReturnType<typeof getLayouts.value>
export type GetMembers = ReturnType<typeof getMembers.value>
export type HideVideoMuted = ReturnType<typeof hideVideoMuted.value>
Expand All @@ -166,6 +212,9 @@ export type SetHideVideoMuted = ReturnType<typeof setHideVideoMuted.value>

export type GetRecordings = ReturnType<typeof getRecordings.value>
export type StartRecording = ReturnType<typeof startRecording.value>

export type GetPlaybacks = ReturnType<typeof getPlaybacks.value>
export type Play = ReturnType<typeof play.value>
// End Room Methods

/**
Expand Down
9 changes: 9 additions & 0 deletions packages/core/src/types/video.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,17 @@ import {
VideoRecordingEvent,
InternalVideoRecordingEventNames,
} from './videoRecording'
import {
VideoPlaybackEventNames,
VideoPlaybackEvent,
InternalVideoPlaybackEventNames,
} from './videoPlayback'

export * from './videoRoomSession'
export * from './videoMember'
export * from './videoLayout'
export * from './videoRecording'
export * from './videoPlayback'

export type RTCTrackEventName = 'track'

Expand All @@ -36,6 +42,7 @@ export type RoomEventNames =
| VideoMemberEventNames
| VideoLayoutEventNames
| VideoRecordingEventNames
| VideoPlaybackEventNames
| RTCTrackEventName

/**
Expand All @@ -48,6 +55,7 @@ export type InternalVideoEventNames =
| InternalVideoMemberEventNames
| InternalVideoLayoutEventNames
| InternalVideoRecordingEventNames
| InternalVideoPlaybackEventNames
| RTCTrackEventName

export type InternalVideoAPIEvent =
Expand All @@ -59,3 +67,4 @@ export type VideoAPIEventParams =
| VideoMemberEvent
| VideoLayoutEvent
| VideoRecordingEvent
| VideoPlaybackEvent
Loading