mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-08 14:11:21 +00:00
GUACAMOLE-25: Merge fixes for audio streaming.
This commit is contained in:
@@ -29,7 +29,26 @@ var Guacamole = Guacamole || {};
|
||||
*/
|
||||
Guacamole.AudioRecorder = function AudioRecorder() {
|
||||
|
||||
// AudioRecorder currently provides no functions
|
||||
/**
|
||||
* Callback which is invoked when the audio recording process has stopped
|
||||
* and the underlying Guacamole stream has been closed normally. Audio will
|
||||
* only resume recording if a new Guacamole.AudioRecorder is started. This
|
||||
* Guacamole.AudioRecorder instance MAY NOT be reused.
|
||||
*
|
||||
* @event
|
||||
*/
|
||||
this.onclose = null;
|
||||
|
||||
/**
|
||||
* Callback which is invoked when the audio recording process cannot
|
||||
* continue due to an error, if it has started at all. The underlying
|
||||
* Guacamole stream is automatically closed. Future attempts to record
|
||||
* audio should not be made, and this Guacamole.AudioRecorder instance
|
||||
* MAY NOT be reused.
|
||||
*
|
||||
* @event
|
||||
*/
|
||||
this.onerror = null;
|
||||
|
||||
};
|
||||
|
||||
@@ -114,6 +133,14 @@ Guacamole.AudioRecorder.getInstance = function getInstance(stream, mimetype) {
|
||||
*/
|
||||
Guacamole.RawAudioRecorder = function RawAudioRecorder(stream, mimetype) {
|
||||
|
||||
/**
|
||||
* Reference to this RawAudioRecorder.
|
||||
*
|
||||
* @private
|
||||
* @type {Guacamole.RawAudioRecorder}
|
||||
*/
|
||||
var recorder = this;
|
||||
|
||||
/**
|
||||
* The size of audio buffer to request from the Web Audio API when
|
||||
* recording or processing audio, in sample-frames. This must be a power of
|
||||
@@ -212,6 +239,14 @@ Guacamole.RawAudioRecorder = function RawAudioRecorder(stream, mimetype) {
|
||||
*/
|
||||
var writtenSamples = 0;
|
||||
|
||||
/**
|
||||
* The audio stream provided by the browser, if allowed. If no stream has
|
||||
* yet been received, this will be null.
|
||||
*
|
||||
* @type MediaStream
|
||||
*/
|
||||
var mediaStream = null;
|
||||
|
||||
/**
|
||||
* The source node providing access to the local audio input device.
|
||||
*
|
||||
@@ -372,31 +407,19 @@ Guacamole.RawAudioRecorder = function RawAudioRecorder(stream, mimetype) {
|
||||
|
||||
};
|
||||
|
||||
// Once audio stream is successfully open, request and begin reading audio
|
||||
writer.onack = function audioStreamAcknowledged(status) {
|
||||
|
||||
// Abort stream if rejected
|
||||
if (status.code !== Guacamole.Status.Code.SUCCESS) {
|
||||
|
||||
// Disconnect media source node from script processor
|
||||
if (source)
|
||||
source.disconnect();
|
||||
|
||||
// Disconnect associated script processor node
|
||||
if (processor)
|
||||
processor.disconnect();
|
||||
|
||||
// Remove references to now-unneeded components
|
||||
processor = null;
|
||||
source = null;
|
||||
|
||||
writer.sendEnd();
|
||||
return;
|
||||
|
||||
}
|
||||
/**
|
||||
* Requests access to the user's microphone and begins capturing audio. All
|
||||
* received audio data is resampled as necessary and forwarded to the
|
||||
* Guacamole stream underlying this Guacamole.RawAudioRecorder. This
|
||||
* function must be invoked ONLY ONCE per instance of
|
||||
* Guacamole.RawAudioRecorder.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
var beginAudioCapture = function beginAudioCapture() {
|
||||
|
||||
// Attempt to retrieve an audio input stream from the browser
|
||||
getUserMedia({ 'audio' : true }, function streamReceived(mediaStream) {
|
||||
getUserMedia({ 'audio' : true }, function streamReceived(stream) {
|
||||
|
||||
// Create processing node which receives appropriately-sized audio buffers
|
||||
processor = context.createScriptProcessor(BUFFER_SIZE, format.channels, format.channels);
|
||||
@@ -408,18 +431,89 @@ Guacamole.RawAudioRecorder = function RawAudioRecorder(stream, mimetype) {
|
||||
};
|
||||
|
||||
// Connect processing node to user's audio input source
|
||||
source = context.createMediaStreamSource(mediaStream);
|
||||
source = context.createMediaStreamSource(stream);
|
||||
source.connect(processor);
|
||||
|
||||
// Save stream for later cleanup
|
||||
mediaStream = stream;
|
||||
|
||||
}, function streamDenied() {
|
||||
|
||||
// Simply end stream if audio access is not allowed
|
||||
writer.sendEnd();
|
||||
|
||||
// Notify of closure
|
||||
if (recorder.onerror)
|
||||
recorder.onerror();
|
||||
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Stops capturing audio, if the capture has started, freeing all associated
|
||||
* resources. If the capture has not started, this function simply ends the
|
||||
* underlying Guacamole stream.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
var stopAudioCapture = function stopAudioCapture() {
|
||||
|
||||
// Disconnect media source node from script processor
|
||||
if (source)
|
||||
source.disconnect();
|
||||
|
||||
// Disconnect associated script processor node
|
||||
if (processor)
|
||||
processor.disconnect();
|
||||
|
||||
// Stop capture
|
||||
if (mediaStream) {
|
||||
var tracks = mediaStream.getTracks();
|
||||
for (var i = 0; i < tracks.length; i++)
|
||||
tracks[i].stop();
|
||||
}
|
||||
|
||||
// Remove references to now-unneeded components
|
||||
processor = null;
|
||||
source = null;
|
||||
mediaStream = null;
|
||||
|
||||
// End stream
|
||||
writer.sendEnd();
|
||||
|
||||
};
|
||||
|
||||
// Once audio stream is successfully open, request and begin reading audio
|
||||
writer.onack = function audioStreamAcknowledged(status) {
|
||||
|
||||
// Begin capture if successful response and not yet started
|
||||
if (status.code === Guacamole.Status.Code.SUCCESS && !mediaStream)
|
||||
beginAudioCapture();
|
||||
|
||||
// Otherwise stop capture and cease handling any further acks
|
||||
else {
|
||||
|
||||
// Stop capturing audio
|
||||
stopAudioCapture();
|
||||
writer.onack = null;
|
||||
|
||||
// Notify if stream has closed normally
|
||||
if (status.code === Guacamole.Status.Code.RESOURCE_CLOSED) {
|
||||
if (recorder.onclose)
|
||||
recorder.onclose();
|
||||
}
|
||||
|
||||
// Otherwise notify of closure due to error
|
||||
else {
|
||||
if (recorder.onerror)
|
||||
recorder.onerror();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
Guacamole.RawAudioRecorder.prototype = new Guacamole.AudioRecorder();
|
||||
|
@@ -648,8 +648,9 @@ Guacamole.Client = function(tunnel) {
|
||||
if (stream.onack)
|
||||
stream.onack(new Guacamole.Status(code, reason));
|
||||
|
||||
// If code is an error, invalidate stream
|
||||
if (code >= 0x0100) {
|
||||
// If code is an error, invalidate stream if not already
|
||||
// invalidated by onack handler
|
||||
if (code >= 0x0100 && output_streams[stream_index] === stream) {
|
||||
stream_indices.free(stream_index);
|
||||
delete output_streams[stream_index];
|
||||
}
|
||||
|
@@ -132,6 +132,14 @@ Guacamole.Status.Code = {
|
||||
*/
|
||||
"RESOURCE_CONFLICT": 0x0205,
|
||||
|
||||
/**
|
||||
* The operation could not be performed as the requested resource is now
|
||||
* closed.
|
||||
*
|
||||
* @type {Number}
|
||||
*/
|
||||
"RESOURCE_CLOSED": 0x0206,
|
||||
|
||||
/**
|
||||
* The operation could not be performed because bad parameters were given.
|
||||
*
|
||||
|
@@ -262,6 +262,36 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector',
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Requests the creation of a new audio stream, recorded from the user's
|
||||
* local audio input device. If audio input is supported by the connection,
|
||||
* an audio stream will be created which will remain open until the remote
|
||||
* desktop requests that it be closed. If the audio stream is successfully
|
||||
* created but is later closed, a new audio stream will automatically be
|
||||
* established to take its place. The mimetype used for all audio streams
|
||||
* produced by this function is defined by
|
||||
* ManagedClient.AUDIO_INPUT_MIMETYPE.
|
||||
*
|
||||
* @param {Guacamole.Client} client
|
||||
* The Guacamole.Client for which the audio stream is being requested.
|
||||
*/
|
||||
var requestAudioStream = function requestAudioStream(client) {
|
||||
|
||||
// Create new audio stream, associating it with an AudioRecorder
|
||||
var stream = client.createAudioStream(ManagedClient.AUDIO_INPUT_MIMETYPE);
|
||||
var recorder = Guacamole.AudioRecorder.getInstance(stream, ManagedClient.AUDIO_INPUT_MIMETYPE);
|
||||
|
||||
// If creation of the AudioRecorder failed, simply end the stream
|
||||
if (!recorder)
|
||||
stream.sendEnd();
|
||||
|
||||
// Otherwise, ensure that another audio stream is created after this
|
||||
// audio stream is closed
|
||||
else
|
||||
recorder.onclose = requestAudioStream.bind(this, client);
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a new ManagedClient, connecting it to the specified connection
|
||||
* or group.
|
||||
@@ -363,9 +393,7 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector',
|
||||
ManagedClientState.ConnectionState.CONNECTED);
|
||||
|
||||
// Begin streaming audio input if possible
|
||||
var stream = client.createAudioStream(ManagedClient.AUDIO_INPUT_MIMETYPE);
|
||||
if (!Guacamole.AudioRecorder.getInstance(stream, ManagedClient.AUDIO_INPUT_MIMETYPE))
|
||||
stream.sendEnd();
|
||||
requestAudioStream(client);
|
||||
|
||||
break;
|
||||
|
||||
|
Reference in New Issue
Block a user