// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/renderer/media/media_stream_dispatcher.h"
#include "base/logging.h"
#include "base/message_loop/message_loop_proxy.h"
#include "content/common/media/media_stream_messages.h"
#include "content/renderer/media/media_stream_dispatcher_eventhandler.h"
#include "content/renderer/render_thread_impl.h"
#include "content/renderer/render_view_impl.h"
#include "url/gurl.h"
namespace content {
namespace {
bool RemoveStreamDeviceFromArray(const StreamDeviceInfo device_info,
StreamDeviceInfoArray* array) {
for (StreamDeviceInfoArray::iterator device_it = array->begin();
device_it != array->end(); ++device_it) {
if (StreamDeviceInfo::IsEqual(*device_it, device_info)) {
array->erase(device_it);
return true;
}
}
return false;
}
} // namespace
// A request is identified by pair (request_id, handler), or ipc_request.
// There could be multiple clients making requests and each has its own
// request_id sequence.
// The ipc_request is garanteed to be unique when it's created in
// MediaStreamDispatcher.
struct MediaStreamDispatcher::Request {
Request(const base::WeakPtr<MediaStreamDispatcherEventHandler>& handler,
int request_id,
int ipc_request)
: handler(handler),
request_id(request_id),
ipc_request(ipc_request) {
}
bool IsThisRequest(
int request_id1,
const base::WeakPtr<MediaStreamDispatcherEventHandler>& handler1) {
return (request_id1 == request_id && handler1.get() == handler.get());
}
base::WeakPtr<MediaStreamDispatcherEventHandler> handler;
int request_id;
int ipc_request;
};
struct MediaStreamDispatcher::Stream {
Stream() {}
~Stream() {}
base::WeakPtr<MediaStreamDispatcherEventHandler> handler;
StreamDeviceInfoArray audio_array;
StreamDeviceInfoArray video_array;
};
MediaStreamDispatcher::MediaStreamDispatcher(RenderViewImpl* render_view)
: RenderViewObserver(render_view),
main_loop_(base::MessageLoopProxy::current()),
next_ipc_id_(0) {
}
MediaStreamDispatcher::~MediaStreamDispatcher() {}
void MediaStreamDispatcher::GenerateStream(
int request_id,
const base::WeakPtr<MediaStreamDispatcherEventHandler>& event_handler,
const StreamOptions& components,
const GURL& security_origin) {
DCHECK(main_loop_->BelongsToCurrentThread());
DVLOG(1) << "MediaStreamDispatcher::GenerateStream(" << request_id << ")";
requests_.push_back(Request(event_handler, request_id, next_ipc_id_));
Send(new MediaStreamHostMsg_GenerateStream(routing_id(),
next_ipc_id_++,
components,
security_origin));
}
void MediaStreamDispatcher::CancelGenerateStream(
int request_id,
const base::WeakPtr<MediaStreamDispatcherEventHandler>& event_handler) {
DCHECK(main_loop_->BelongsToCurrentThread());
DVLOG(1) << "MediaStreamDispatcher::CancelGenerateStream"
<< ", {request_id = " << request_id << "}";
RequestList::iterator it = requests_.begin();
for (; it != requests_.end(); ++it) {
if (it->IsThisRequest(request_id, event_handler)) {
int ipc_request = it->ipc_request;
requests_.erase(it);
Send(new MediaStreamHostMsg_CancelGenerateStream(routing_id(),
ipc_request));
break;
}
}
}
void MediaStreamDispatcher::StopStreamDevice(
const StreamDeviceInfo& device_info) {
DVLOG(1) << "MediaStreamDispatcher::StopStreamDevice"
<< ", {device_id = " << device_info.device.id << "}";
// Remove |device_info| from all streams in |label_stream_map_|.
bool device_found = false;
LabelStreamMap::iterator stream_it = label_stream_map_.begin();
while (stream_it != label_stream_map_.end()) {
StreamDeviceInfoArray& audio_array = stream_it->second.audio_array;
StreamDeviceInfoArray& video_array = stream_it->second.video_array;
if (RemoveStreamDeviceFromArray(device_info, &audio_array) ||
RemoveStreamDeviceFromArray(device_info, &video_array)) {
device_found = true;
if (audio_array.empty() && video_array.empty()) {
label_stream_map_.erase(stream_it++);
continue;
}
}
++stream_it;
}
DCHECK(device_found);
Send(new MediaStreamHostMsg_StopStreamDevice(routing_id(),
device_info.device.id));
}
void MediaStreamDispatcher::EnumerateDevices(
int request_id,
const base::WeakPtr<MediaStreamDispatcherEventHandler>& event_handler,
MediaStreamType type,
const GURL& security_origin) {
DCHECK(main_loop_->BelongsToCurrentThread());
DCHECK(type == MEDIA_DEVICE_AUDIO_CAPTURE ||
type == MEDIA_DEVICE_VIDEO_CAPTURE);
DVLOG(1) << "MediaStreamDispatcher::EnumerateDevices("
<< request_id << ")";
for (RequestList::iterator it = requests_.begin(); it != requests_.end();
++it) {
DCHECK(!it->IsThisRequest(request_id, event_handler));
}
requests_.push_back(Request(event_handler, request_id, next_ipc_id_));
Send(new MediaStreamHostMsg_EnumerateDevices(routing_id(),
next_ipc_id_++,
type,
security_origin));
}
void MediaStreamDispatcher::StopEnumerateDevices(
int request_id,
const base::WeakPtr<MediaStreamDispatcherEventHandler>& event_handler) {
DCHECK(main_loop_->BelongsToCurrentThread());
DVLOG(1) << "MediaStreamDispatcher::StopEnumerateDevices("
<< request_id << ")";
for (RequestList::iterator it = requests_.begin(); it != requests_.end();
++it) {
if (it->IsThisRequest(request_id, event_handler)) {
Send(new MediaStreamHostMsg_CancelEnumerateDevices(routing_id(),
it->ipc_request));
requests_.erase(it);
break;
}
}
}
void MediaStreamDispatcher::OpenDevice(
int request_id,
const base::WeakPtr<MediaStreamDispatcherEventHandler>& event_handler,
const std::string& device_id,
MediaStreamType type,
const GURL& security_origin) {
DCHECK(main_loop_->BelongsToCurrentThread());
DVLOG(1) << "MediaStreamDispatcher::OpenDevice(" << request_id << ")";
requests_.push_back(Request(event_handler, request_id, next_ipc_id_));
Send(new MediaStreamHostMsg_OpenDevice(routing_id(),
next_ipc_id_++,
device_id,
type,
security_origin));
}
void MediaStreamDispatcher::CancelOpenDevice(
int request_id,
const base::WeakPtr<MediaStreamDispatcherEventHandler>& event_handler) {
CancelGenerateStream(request_id, event_handler);
}
void MediaStreamDispatcher::CloseDevice(const std::string& label) {
DCHECK(main_loop_->BelongsToCurrentThread());
DCHECK(!label.empty());
DVLOG(1) << "MediaStreamDispatcher::CloseDevice"
<< ", {label = " << label << "}";
LabelStreamMap::iterator it = label_stream_map_.find(label);
if (it == label_stream_map_.end())
return;
label_stream_map_.erase(it);
Send(new MediaStreamHostMsg_CloseDevice(routing_id(), label));
}
bool MediaStreamDispatcher::Send(IPC::Message* message) {
if (!RenderThread::Get()) {
delete message;
return false;
}
return RenderThread::Get()->Send(message);
}
bool MediaStreamDispatcher::OnMessageReceived(const IPC::Message& message) {
bool handled = true;
IPC_BEGIN_MESSAGE_MAP(MediaStreamDispatcher, message)
IPC_MESSAGE_HANDLER(MediaStreamMsg_StreamGenerated,
OnStreamGenerated)
IPC_MESSAGE_HANDLER(MediaStreamMsg_StreamGenerationFailed,
OnStreamGenerationFailed)
IPC_MESSAGE_HANDLER(MediaStreamMsg_DeviceStopped,
OnDeviceStopped)
IPC_MESSAGE_HANDLER(MediaStreamMsg_DevicesEnumerated,
OnDevicesEnumerated)
IPC_MESSAGE_HANDLER(MediaStreamMsg_DeviceOpened,
OnDeviceOpened)
IPC_MESSAGE_HANDLER(MediaStreamMsg_DeviceOpenFailed,
OnDeviceOpenFailed)
IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP()
return handled;
}
void MediaStreamDispatcher::OnStreamGenerated(
int request_id,
const std::string& label,
const StreamDeviceInfoArray& audio_array,
const StreamDeviceInfoArray& video_array) {
DCHECK(main_loop_->BelongsToCurrentThread());
for (RequestList::iterator it = requests_.begin();
it != requests_.end(); ++it) {
Request& request = *it;
if (request.ipc_request == request_id) {
Stream new_stream;
new_stream.handler = request.handler;
new_stream.audio_array = audio_array;
new_stream.video_array = video_array;
label_stream_map_[label] = new_stream;
if (request.handler.get()) {
request.handler->OnStreamGenerated(
request.request_id, label, audio_array, video_array);
DVLOG(1) << "MediaStreamDispatcher::OnStreamGenerated("
<< request.request_id << ", " << label << ")";
}
requests_.erase(it);
break;
}
}
}
void MediaStreamDispatcher::OnStreamGenerationFailed(int request_id) {
DCHECK(main_loop_->BelongsToCurrentThread());
for (RequestList::iterator it = requests_.begin();
it != requests_.end(); ++it) {
Request& request = *it;
if (request.ipc_request == request_id) {
if (request.handler.get()) {
request.handler->OnStreamGenerationFailed(request.request_id);
DVLOG(1) << "MediaStreamDispatcher::OnStreamGenerationFailed("
<< request.request_id << ")\n";
}
requests_.erase(it);
break;
}
}
}
void MediaStreamDispatcher::OnDeviceStopped(
const std::string& label,
const StreamDeviceInfo& device_info) {
DCHECK(main_loop_->BelongsToCurrentThread());
DVLOG(1) << "MediaStreamDispatcher::OnDeviceStopped("
<< "{label = " << label << "})"
<< ", {device_id = " << device_info.device.id << "})";
LabelStreamMap::iterator it = label_stream_map_.find(label);
if (it == label_stream_map_.end()) {
// This can happen if a user happen stop a the device from JS at the same
// time as the underlying media device is unplugged from the system.
return;
}
Stream* stream = &it->second;
if (IsAudioMediaType(device_info.device.type))
RemoveStreamDeviceFromArray(device_info, &stream->audio_array);
else
RemoveStreamDeviceFromArray(device_info, &stream->video_array);
if (stream->handler.get())
stream->handler->OnDeviceStopped(label, device_info);
if (stream->audio_array.empty() && stream->video_array.empty())
label_stream_map_.erase(it);
}
void MediaStreamDispatcher::OnDevicesEnumerated(
int request_id,
const StreamDeviceInfoArray& device_array) {
DCHECK(main_loop_->BelongsToCurrentThread());
DCHECK_GE(request_id, 0);
for (RequestList::iterator it = requests_.begin(); it != requests_.end();
++it) {
if (it->ipc_request == request_id && it->handler.get()) {
it->handler->OnDevicesEnumerated(it->request_id, device_array);
break;
}
}
}
void MediaStreamDispatcher::OnDeviceOpened(
int request_id,
const std::string& label,
const StreamDeviceInfo& device_info) {
DCHECK(main_loop_->BelongsToCurrentThread());
for (RequestList::iterator it = requests_.begin();
it != requests_.end(); ++it) {
Request& request = *it;
if (request.ipc_request == request_id) {
Stream new_stream;
new_stream.handler = request.handler;
if (IsAudioMediaType(device_info.device.type)) {
new_stream.audio_array.push_back(device_info);
} else if (IsVideoMediaType(device_info.device.type)) {
new_stream.video_array.push_back(device_info);
} else {
NOTREACHED();
}
label_stream_map_[label] = new_stream;
if (request.handler.get()) {
request.handler->OnDeviceOpened(request.request_id, label, device_info);
DVLOG(1) << "MediaStreamDispatcher::OnDeviceOpened("
<< request.request_id << ", " << label << ")";
}
requests_.erase(it);
break;
}
}
}
void MediaStreamDispatcher::OnDeviceOpenFailed(int request_id) {
DCHECK(main_loop_->BelongsToCurrentThread());
for (RequestList::iterator it = requests_.begin();
it != requests_.end(); ++it) {
Request& request = *it;
if (request.ipc_request == request_id) {
if (request.handler.get()) {
request.handler->OnDeviceOpenFailed(request.request_id);
DVLOG(1) << "MediaStreamDispatcher::OnDeviceOpenFailed("
<< request.request_id << ")\n";
}
requests_.erase(it);
break;
}
}
}
int MediaStreamDispatcher::audio_session_id(const std::string& label,
int index) {
LabelStreamMap::iterator it = label_stream_map_.find(label);
if (it == label_stream_map_.end() ||
it->second.audio_array.size() <= static_cast<size_t>(index)) {
return StreamDeviceInfo::kNoId;
}
return it->second.audio_array[index].session_id;
}
bool MediaStreamDispatcher::IsStream(const std::string& label) {
return label_stream_map_.find(label) != label_stream_map_.end();
}
int MediaStreamDispatcher::video_session_id(const std::string& label,
int index) {
LabelStreamMap::iterator it = label_stream_map_.find(label);
if (it == label_stream_map_.end() ||
it->second.video_array.size() <= static_cast<size_t>(index)) {
return StreamDeviceInfo::kNoId;
}
return it->second.video_array[index].session_id;
}
} // namespace content