import { makeAction, AsyncAction } from '../async' import { MEDIA_AUDIO_CONSTRAINT_SET, MEDIA_VIDEO_CONSTRAINT_SET, MEDIA_ENUMERATE, MEDIA_STREAM, ME, ME_DESKTOP } from '../constants' import _debug from 'debug' const debug = _debug('peercalls') export interface MediaDevice { id: string name: string type: 'audioinput' | 'videoinput' } export const enumerateDevices = makeAction(MEDIA_ENUMERATE, async () => { const devices = await navigator.mediaDevices.enumerateDevices() return devices .filter( device => device.kind === 'audioinput' || device.kind === 'videoinput') .map(device => ({ id: device.deviceId, type: device.kind, name: device.label, }) as MediaDevice) }) export type FacingMode = 'user' | 'environment' export interface DeviceConstraint { deviceId: string } export interface FacingConstraint { facingMode: FacingMode | { exact: FacingMode } } export type VideoConstraint = DeviceConstraint | boolean | FacingConstraint export type AudioConstraint = DeviceConstraint | boolean export interface GetMediaConstraints { video: VideoConstraint audio: AudioConstraint } declare global { interface Navigator { webkitGetUserMedia?: typeof navigator.getUserMedia mozGetUserMedia?: typeof navigator.getUserMedia } } async function getUserMedia( constraints: MediaStreamConstraints, ): Promise { if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { return navigator.mediaDevices.getUserMedia(constraints) } const _getUserMedia: typeof navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia return new Promise((resolve, reject) => { _getUserMedia.call(navigator, constraints, resolve, reject) }) } async function getDisplayMedia(): Promise { const mediaDevices = navigator.mediaDevices as any // eslint-disable-line return mediaDevices.getDisplayMedia({video: true, audio: false}) } export interface MediaVideoConstraintAction { type: 'MEDIA_VIDEO_CONSTRAINT_SET' payload: VideoConstraint } export interface MediaAudioConstraintAction { type: 'MEDIA_AUDIO_CONSTRAINT_SET' payload: AudioConstraint } export function setVideoConstraint( payload: VideoConstraint, ): MediaVideoConstraintAction { return { type: MEDIA_VIDEO_CONSTRAINT_SET, payload, } } export function setAudioConstraint( payload: AudioConstraint, ): MediaAudioConstraintAction { return { type: MEDIA_AUDIO_CONSTRAINT_SET, payload, } } export const play = makeAction('MEDIA_PLAY', async () => { const promises = Array .from(document.querySelectorAll('video')) .filter(video => video.paused) .map(video => video.play()) await Promise.all(promises) }) export const getMediaStream = makeAction( MEDIA_STREAM, async (constraints: GetMediaConstraints) => { debug('getMediaStream', constraints) const payload: MediaStreamPayload = { stream: await getUserMedia(constraints), userId: ME, } return payload }, ) export const getDesktopStream = makeAction( MEDIA_STREAM, async () => { debug('getDesktopStream') const payload: MediaStreamPayload = { stream: await getDisplayMedia(), userId: ME_DESKTOP, } return payload }, ) export interface MediaStreamPayload { stream: MediaStream userId: string } export type MediaEnumerateAction = AsyncAction<'MEDIA_ENUMERATE', MediaDevice[]> export type MediaStreamAction = AsyncAction<'MEDIA_STREAM', MediaStreamPayload> export type MediaPlayAction = AsyncAction<'MEDIA_PLAY', void> export type MediaAction = MediaVideoConstraintAction | MediaAudioConstraintAction | MediaEnumerateAction | MediaStreamAction | MediaPlayAction