Skip to content
This repository was archived by the owner on Dec 15, 2020. It is now read-only.

Commit c0ef14d

Browse files
larrylin28facebook-github-bot
authored andcommitted
Dispatch More Media Events from Audio Source
Summary: To support more audio control, more media events is needed. Support dispatching `durationchange`, `timeupdate`, `pause`, `playing`, `error` events from `VRAudioBufferSource` Differential Revision: D4922091 fbshipit-source-id: 549161e
1 parent 9a73f59 commit c0ef14d

5 files changed

Lines changed: 116 additions & 28 deletions

File tree

ReactVR/js/Audio/MediaError.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/**
2+
* Copyright (c) 2015-present, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*
9+
* @flow
10+
*/
11+
12+
/**
13+
* Custom media errors class for audio, correspond to HTML media error
14+
*/
15+
16+
export const MEDIA_ERR_ABORTED = 1; // W3C error code, the fetching was aborted by the user's request.
17+
export const MEDIA_ERR_NETWORK = 2; // W3C error code, the fetching was failed by network error.
18+
export const MEDIA_ERR_DECODE = 3; // W3C error code, an error occurred while trying to decode the media.
19+
export const MEDIA_ERR_SRC_NOT_SUPPORTED = 4; // W3C error code, the media has been found to be unsuitable.
20+
21+
export default class MediaError {
22+
code: number;
23+
message: string;
24+
25+
constructor(code: number, message: string) {
26+
this.code = code;
27+
this.message = message;
28+
}
29+
}

ReactVR/js/Audio/VRAudioBufferManager.js

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,11 @@
88
*
99
* @flow
1010
*/
11+
import {MEDIA_ERR_NETWORK, MEDIA_ERR_DECODE, default as MediaError} from './MediaError';
1112

1213
import type VRAudioContext from './VRAudioContext';
1314

14-
type OnLoadHandler = (?AudioBuffer, any) => void;
15+
type OnLoadHandler = (buffer: ?AudioBuffer, error: ?MediaError) => void;
1516

1617
// A simple dictionary cache and a simple request batch
1718
// TODO: we should use expire time to update/clean cache
@@ -47,20 +48,29 @@ export function fetch(url: string, audioContext: VRAudioContext, onLoad: OnLoadH
4748
function(message) {
4849
_onRequestError(
4950
url,
50-
'[VRAudioBufferSource] Decoding failure: ' + url + ' (' + message + ')'
51+
new MediaError(
52+
MEDIA_ERR_DECODE,
53+
'[VRAudioBufferSource] Decoding failure: ' + url + ' (' + message + ')'
54+
)
5155
);
5256
}
5357
);
5458
} else {
5559
_onRequestError(
5660
url,
57-
'[VRAudioBufferSource] XHR Error: ' + url + ' (' + xhr.statusText + ')'
61+
new MediaError(
62+
MEDIA_ERR_NETWORK,
63+
'[VRAudioBufferSource] XHR Error: ' + url + ' (' + xhr.statusText + ')'
64+
)
5865
);
5966
}
6067
};
6168

6269
xhr.onerror = function(event) {
63-
_onRequestError(url, '[VRAudioBufferSource] XHR Network failure: ' + url);
70+
_onRequestError(
71+
url,
72+
new MediaError(MEDIA_ERR_NETWORK, '[VRAudioBufferSource] XHR Network failure: ' + url)
73+
);
6474
};
6575

6676
xhr.send();
@@ -90,7 +100,7 @@ function _onRequestSucceed(url: string, buffer: AudioBuffer) {
90100
});
91101
}
92102

93-
function _onRequestError(url: string, error: any) {
103+
function _onRequestError(url: string, error: MediaError) {
94104
const pendingRequest = _pendingRequest[url];
95105
delete _pendingRequest[url];
96106
pendingRequest.map(_onLoad => {

ReactVR/js/Audio/VRAudioBufferSource.js

Lines changed: 61 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,21 @@
1010
*/
1111

1212
import * as VRAudioBufferManager from './VRAudioBufferManager';
13+
import MediaError from './MediaError';
1314

1415
import type VRAudioContext from './VRAudioContext';
1516

17+
type MediaEventType =
18+
| 'canplay'
19+
| 'ended'
20+
| 'durationchange'
21+
| 'timeupdate'
22+
| 'pause'
23+
| 'playing'
24+
| 'error';
25+
1626
export type MediaEvent = {
17-
type: 'canplay' | 'ended',
27+
type: MediaEventType,
1828
timeStamp: number,
1929
target: any,
2030
};
@@ -31,12 +41,16 @@ export default class VRAudioBufferSource {
3141
_playbackTime: number;
3242
_startTime: number;
3343
_isPlaying: boolean;
44+
_error: ?MediaError;
45+
_ended: boolean;
3446

3547
constructor(vrAudioContext: VRAudioContext) {
3648
this._vrAudioContext = vrAudioContext;
3749
this._playbackTime = 0;
3850
this._startTime = 0;
3951
this._isPlaying = false;
52+
this._error = null;
53+
this._ended = false;
4054
this.onMediaEvent = undefined;
4155
}
4256

@@ -49,23 +63,42 @@ export default class VRAudioBufferSource {
4963
this.stopSourceNode();
5064
this._playbackTime = 0;
5165
this._buffer = null;
66+
this._error = null;
67+
this._ended = false;
5268

5369
this.url = url;
54-
VRAudioBufferManager.fetch(url, this._vrAudioContext, (buffer: ?AudioBuffer, error) => {
55-
if (error) {
56-
console.warn('Failed to fetch audio:', url);
57-
} else {
58-
this._buffer = buffer;
59-
this._onMediaEvent({
60-
type: 'canplay',
61-
timeStamp: Date.now(),
62-
target: {
63-
ended: false,
64-
error: null,
65-
},
66-
});
70+
VRAudioBufferManager.fetch(
71+
url,
72+
this._vrAudioContext,
73+
(buffer: ?AudioBuffer, error: ?MediaError) => {
74+
if (error) {
75+
console.warn('Failed to fetch audio:', url);
76+
this._error = error;
77+
this._onMediaEvent(this.createMediaEvent('error'));
78+
} else {
79+
this._buffer = buffer;
80+
this._onMediaEvent(this.createMediaEvent('canplay'));
81+
this._onMediaEvent(this.createMediaEvent('durationchange'));
82+
}
6783
}
68-
});
84+
);
85+
}
86+
87+
createMediaEvent(type: MediaEventType): MediaEvent {
88+
const duration = this._buffer ? this._buffer.duration : 0;
89+
const currentTime = this._isPlaying
90+
? this._vrAudioContext.getWebAudioContext().currentTime - this._startTime + this._playbackTime
91+
: this._playbackTime;
92+
return {
93+
type: type,
94+
timeStamp: Date.now(),
95+
target: {
96+
currentTime: currentTime,
97+
duration: duration,
98+
ended: this._ended,
99+
error: this._error,
100+
},
101+
};
69102
}
70103

71104
getSourceNode() {
@@ -81,6 +114,7 @@ export default class VRAudioBufferSource {
81114
play() {
82115
if (this._isPlaying) return;
83116
this.playSourceNode();
117+
this._onMediaEvent(this.createMediaEvent('playing'));
84118
}
85119

86120
playSourceNode() {
@@ -91,21 +125,16 @@ export default class VRAudioBufferSource {
91125
sourceNode.onended = () => {
92126
this.stopSourceNode();
93127
this._playbackTime = 0;
94-
this._onMediaEvent({
95-
type: 'ended',
96-
timeStamp: Date.now(),
97-
target: {
98-
ended: true,
99-
error: null,
100-
},
101-
});
128+
this._ended = true;
129+
this._onMediaEvent(this.createMediaEvent('ended'));
102130
};
103131
if (!this._buffer) {
104132
console.warn('play() called before audio loaded for url', this.url);
105133
return;
106134
}
107135
sourceNode.buffer = this._buffer;
108136
sourceNode.start(0, this._playbackTime);
137+
this._ended = false;
109138
this._isPlaying = true;
110139
this._startTime = this._vrAudioContext.getWebAudioContext().currentTime;
111140
}
@@ -138,13 +167,16 @@ export default class VRAudioBufferSource {
138167
this._playbackTime = this._vrAudioContext.getWebAudioContext().currentTime -
139168
this._startTime +
140169
this._playbackTime;
170+
this._onMediaEvent(this.createMediaEvent('pause'));
141171
}
142172

143173
stop() {
144174
if (!this._isPlaying) return;
145175

146176
this.stopSourceNode();
147177
this._playbackTime = 0;
178+
this._ended = true;
179+
this._onMediaEvent(this.createMediaEvent('ended'));
148180
}
149181

150182
stopSourceNode() {
@@ -157,6 +189,12 @@ export default class VRAudioBufferSource {
157189
this._isPlaying = false;
158190
}
159191

192+
frame() {
193+
if (this._isPlaying) {
194+
this._onMediaEvent(this.createMediaEvent('timeupdate'));
195+
}
196+
}
197+
160198
dispose() {
161199
this.stopSourceNode();
162200
if (this.url && this._buffer) {

ReactVR/js/Audio/VRAudioComponent.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,12 @@ export default class VRAudioComponent {
173173
}
174174
}
175175

176+
frame() {
177+
if (this._source) {
178+
this._source.frame();
179+
}
180+
}
181+
176182
dispose() {
177183
this._disconnectNodes();
178184
this._freeSource();

ReactVR/js/Modules/RCTAudioModule.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ const MEDIA_EVENT_CALLBACK_NAME = {
2525
ended: 'onAudioEnded',
2626
error: 'onAudioError',
2727
timeupdate: 'onAudioTimeUpdate',
28+
playing: 'onAudioPlaying',
29+
pause: 'onAudioPause',
2830
};
2931

3032
/**
@@ -237,5 +239,8 @@ export default class RCTAudioModule extends Module {
237239
if (this.audioContext) {
238240
this.audioContext.frame(camera);
239241
}
242+
for (const key in this._components) {
243+
this._components[key].frame();
244+
}
240245
}
241246
}

0 commit comments

Comments
 (0)