DeviceSelector.tsx 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. import {
  2. DeviceList,
  3. LocalMediaList,
  4. Media,
  5. RequestUserMedia
  6. } from '@andyet/simplewebrtc';
  7. import React, { Component } from 'react';
  8. // CaptureControls renders DeviceSelectors and MediaPreviews, which allows a
  9. // user to select their desired camera and microphone.
  10. interface UserMediaIds {
  11. audio?: string;
  12. video?: string;
  13. }
  14. interface DeviceSelectorRenderProps {
  15. hasDevice?: boolean;
  16. permissionDenied?: boolean;
  17. requestingCapture?: boolean;
  18. requestPermissions?: () => Promise<UserMediaIds>;
  19. devices?: MediaDeviceInfo[];
  20. currentMedia?: Media;
  21. selectMedia?: (deviceId?: string) => void;
  22. }
  23. interface Props {
  24. kind: 'video' | 'audio';
  25. render: (props: DeviceSelectorRenderProps) => React.ReactNode;
  26. }
  27. interface State {
  28. mediaTypeDisabled: boolean;
  29. }
  30. export default class DeviceSelector extends Component<Props, State> {
  31. constructor(props: Props) {
  32. super(props);
  33. this.state = {
  34. mediaTypeDisabled: false
  35. };
  36. }
  37. public render() {
  38. const { kind, render } = this.props;
  39. return (
  40. <DeviceList
  41. videoInput={kind === 'video'}
  42. audioInput={kind === 'audio'}
  43. render={({
  44. devices,
  45. hasCamera,
  46. cameraPermissionDenied,
  47. hasMicrophone,
  48. microphonePermissionDenied,
  49. requestingCameraCapture,
  50. requestingMicrophoneCapture,
  51. requestingCapture
  52. }) => {
  53. if (
  54. (kind === 'video' && !hasCamera && !cameraPermissionDenied) ||
  55. (kind === 'audio' && !hasMicrophone && !microphonePermissionDenied)
  56. ) {
  57. return render({ hasDevice: false });
  58. }
  59. if (
  60. (kind === 'video' && cameraPermissionDenied) ||
  61. (kind === 'audio' && microphonePermissionDenied)
  62. ) {
  63. return render({ permissionDenied: true });
  64. }
  65. if (
  66. (kind === 'video' && requestingCameraCapture) ||
  67. (kind === 'audio' && requestingMicrophoneCapture)
  68. ) {
  69. return render({ requestingCapture: true });
  70. }
  71. return (
  72. <LocalMediaList
  73. screen={false}
  74. render={({ media, removeMedia }) => {
  75. const videoStreams = media.filter(m => m.kind === 'video');
  76. const audioStreams = media.filter(m => m.kind === 'audio');
  77. const mediaForKind =
  78. kind === 'video' ? videoStreams : audioStreams;
  79. const existingNonKindMedia =
  80. kind === 'video' ? audioStreams : videoStreams;
  81. if (!devices.length || this.state.mediaTypeDisabled) {
  82. return (
  83. <RequestUserMedia
  84. video={kind === 'video'}
  85. audio={kind === 'audio'}
  86. share={false}
  87. render={getMedia => {
  88. return render({
  89. requestPermissions: () => {
  90. this.setState({ mediaTypeDisabled: false });
  91. return getMedia({
  92. audio: kind === 'audio',
  93. video: kind === 'video'
  94. });
  95. }
  96. });
  97. }}
  98. />
  99. );
  100. } else if (!mediaForKind.length) {
  101. return (
  102. <RequestUserMedia
  103. replaceVideo={
  104. videoStreams.length > 0
  105. ? videoStreams[videoStreams.length - 1].id
  106. : undefined
  107. }
  108. replaceAudio={
  109. audioStreams.length > 0
  110. ? audioStreams[audioStreams.length - 1].id
  111. : undefined
  112. }
  113. share={false}
  114. render={getMedia => {
  115. if (!requestingCapture) {
  116. const constraints: MediaStreamConstraints = {
  117. audio:
  118. kind === 'audio' ||
  119. (kind === 'video' &&
  120. existingNonKindMedia.length > 0),
  121. video:
  122. kind === 'video' ||
  123. (kind === 'audio' &&
  124. existingNonKindMedia.length > 0)
  125. };
  126. getMedia(constraints);
  127. }
  128. return null;
  129. }}
  130. />
  131. );
  132. }
  133. const latestMedia = mediaForKind[mediaForKind.length - 1];
  134. const latestOtherMedia =
  135. existingNonKindMedia[existingNonKindMedia.length - 1];
  136. return (
  137. <RequestUserMedia
  138. replaceVideo={
  139. kind === 'video' && latestMedia
  140. ? latestMedia.id
  141. : latestOtherMedia
  142. ? latestOtherMedia.id
  143. : undefined
  144. }
  145. replaceAudio={
  146. kind === 'audio' && latestMedia
  147. ? latestMedia.id
  148. : latestOtherMedia
  149. ? latestOtherMedia.id
  150. : undefined
  151. }
  152. share={false}
  153. render={getMedia => {
  154. return render({
  155. currentMedia: latestMedia,
  156. devices,
  157. hasDevice: true,
  158. selectMedia: deviceId => {
  159. if (deviceId) {
  160. if (this.state.mediaTypeDisabled) {
  161. this.setState({ mediaTypeDisabled: false });
  162. }
  163. const getMediaOptions: MediaStreamConstraints = {
  164. [kind === 'video' ? 'video' : 'audio']: {
  165. deviceId: { exact: deviceId }
  166. }
  167. };
  168. if (existingNonKindMedia.length > 0) {
  169. getMediaOptions[existingNonKindMedia[0].kind] = {
  170. deviceId: {
  171. exact: existingNonKindMedia[0].track.getSettings()
  172. .deviceId
  173. }
  174. };
  175. }
  176. getMedia(getMediaOptions);
  177. } else {
  178. this.setState({ mediaTypeDisabled: true }, () => {
  179. for (const m of media) {
  180. removeMedia!(m.id);
  181. }
  182. });
  183. }
  184. }
  185. });
  186. }}
  187. />
  188. );
  189. }}
  190. />
  191. );
  192. }}
  193. />
  194. );
  195. }
  196. }