diff --git a/sky/services/media/BUILD.gn b/sky/services/media/BUILD.gn index bfa0fe938b5..d279ac4f292 100644 --- a/sky/services/media/BUILD.gn +++ b/sky/services/media/BUILD.gn @@ -46,6 +46,8 @@ if (is_android) { "ios/media_player_impl.mm", "ios/media_service_impl.h", "ios/media_service_impl.mm", + "ios/sound_pool_impl.h", + "ios/sound_pool_impl.mm", ] deps = [ diff --git a/sky/services/media/ios/media_service_impl.h b/sky/services/media/ios/media_service_impl.h index e6bfee45733..9635e556ae3 100644 --- a/sky/services/media/ios/media_service_impl.h +++ b/sky/services/media/ios/media_service_impl.h @@ -10,6 +10,7 @@ #include "mojo/public/cpp/bindings/strong_binding.h" #include "sky/services/media/media.mojom.h" #include "sky/services/media/ios/media_player_impl.h" +#include "sky/services/media/ios/sound_pool_impl.h" namespace sky { namespace services { @@ -29,6 +30,7 @@ class MediaServiceImpl : public ::media::MediaService { private: mojo::StrongBinding<::media::MediaService> binding_; MediaPlayerFactory media_player_; + SoundPoolFactory sound_pool_; DISALLOW_COPY_AND_ASSIGN(MediaServiceImpl); }; diff --git a/sky/services/media/ios/media_service_impl.mm b/sky/services/media/ios/media_service_impl.mm index bda140722e5..9f2762a3eb2 100644 --- a/sky/services/media/ios/media_service_impl.mm +++ b/sky/services/media/ios/media_service_impl.mm @@ -23,7 +23,7 @@ void MediaServiceImpl::CreatePlayer( void MediaServiceImpl::CreateSoundPool( mojo::InterfaceRequest<::media::SoundPool> pool, int32_t max_streams) { - DCHECK(false); + sound_pool_.Create(nullptr, pool.Pass()); } void MediaServiceFactory::Create( diff --git a/sky/services/media/ios/sound_pool_impl.h b/sky/services/media/ios/sound_pool_impl.h new file mode 100644 index 00000000000..b21d3ee9186 --- /dev/null +++ b/sky/services/media/ios/sound_pool_impl.h @@ -0,0 +1,76 @@ +// Copyright 2016 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. + +#ifndef SKY_SERVICES_MEDIA_IOS_SOUND_POOL_IMPL_H_ +#define SKY_SERVICES_MEDIA_IOS_SOUND_POOL_IMPL_H_ + +#include "base/macros.h" +#include "mojo/public/cpp/application/interface_factory.h" +#include "mojo/public/cpp/bindings/strong_binding.h" +#include "sky/services/media/media.mojom.h" +#include "sky/services/media/ios/media_player_impl.h" + +#if __OBJC__ +@class SoundPoolClient; +#else // __OBJC__ +class SoundPoolClient; +#endif // __OBJC__ + +namespace sky { +namespace services { +namespace media { + +class SoundPoolImpl : public ::media::SoundPool { + public: + explicit SoundPoolImpl(mojo::InterfaceRequest<::media::SoundPool> request); + + ~SoundPoolImpl() override; + + void Load(mojo::ScopedDataPipeConsumerHandle data_source, + const ::media::SoundPool::LoadCallback& callback) override; + + void Play(int32_t sound_id, + int32_t stream_id, + mojo::Array channel_volumes, + bool loop, + float rate, + const ::media::SoundPool::PlayCallback& callback) override; + + void Stop(int32_t stream_id) override; + + void Pause(int32_t stream_id) override; + + void Resume(int32_t stream_id) override; + + void SetRate(int32_t stream_id, float rate) override; + + void SetVolume(int32_t stream_id, + mojo::Array channel_volumes) override; + + void PauseAll() override; + + void ResumeAll() override; + + private: + mojo::StrongBinding<::media::SoundPool> binding_; + SoundPoolClient* sound_pool_; + + void onCopyToTemp(const ::media::SoundPool::LoadCallback& callback, + base::FilePath path, + bool success); + + DISALLOW_COPY_AND_ASSIGN(SoundPoolImpl); +}; + +class SoundPoolFactory : public mojo::InterfaceFactory<::media::SoundPool> { + public: + void Create(mojo::ApplicationConnection* connection, + mojo::InterfaceRequest<::media::SoundPool> request) override; +}; + +} // namespace media +} // namespace services +} // namespace sky + +#endif // SKY_SERVICES_MEDIA_IOS_SOUND_POOL_IMPL_H_ diff --git a/sky/services/media/ios/sound_pool_impl.mm b/sky/services/media/ios/sound_pool_impl.mm new file mode 100644 index 00000000000..851e93cff2d --- /dev/null +++ b/sky/services/media/ios/sound_pool_impl.mm @@ -0,0 +1,295 @@ +// Copyright 2016 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 "sky/services/media/ios/sound_pool_impl.h" + +#include "base/bind.h" +#include "base/mac/scoped_nsautorelease_pool.h" +#include "base/message_loop/message_loop.h" +#include "mojo/data_pipe_utils/data_pipe_utils.h" + +#import +#include + +@interface SoundPoolClient : NSObject + +- (BOOL)loadPlayer:(NSURL*)url streamOut:(int32_t*)stream; + +- (BOOL)play:(int32_t)stream; + +- (BOOL)play:(int32_t)stream + volume:(float)volume + loop:(BOOL)loop + rate:(float)rate; + +- (BOOL)resume:(int32_t)stream; + +- (void)stop:(int32_t)stream; + +- (void)pause:(int32_t)stream; + +- (void)setRate:(float)rate stream:(int32_t)stream; + +- (void)setVolume:(float)volume stream:(int32_t)stream; + +- (void)pauseAll; + +- (void)resumeAll; + +@end + +@implementation SoundPoolClient { + NSMutableDictionary* _players; // keyed by stream ID + int32_t _lastID; +} + +- (instancetype)init { + self = [super init]; + + if (self) { + _players = [[NSMutableDictionary alloc] init]; + } + + return self; +} + +- (BOOL)loadPlayer:(NSURL*)url streamOut:(int32_t*)stream { + if (stream == NULL) { + return NO; + } + + NSError* error = NULL; + AVAudioPlayer* player = + [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error]; + + if (error != NULL) { + [player release]; + return NO; + } + + if (![player prepareToPlay]) { + [player release]; + return NO; + } + + int32_t streamID = ++_lastID; + _players[@(streamID)] = player; + [player release]; + + *stream = streamID; + return YES; +} + +- (AVAudioPlayer*)playerForID:(int32_t)stream { + return _players[@(stream)]; +} + +- (BOOL)play:(int32_t)stream { + return [[self playerForID:stream] playAtTime:0.0]; +} + +- (BOOL)play:(int32_t)stream + volume:(float)volume + loop:(BOOL)loop + rate:(float)rate { + AVAudioPlayer* player = [self playerForID:stream]; + + if (player == NULL) { + return NO; + } + + if (volume > 1.0) { + volume = 1.0; + } + + if (volume < 0.0) { + volume = 0.0; + } + + player.volume = volume; + player.numberOfLoops = loop ? -1 : 0; + player.rate = rate; + return [player playAtTime:0.0]; +} + +- (BOOL)resume:(int32_t)stream { + return [[self playerForID:stream] play]; +} + +- (void)stop:(int32_t)stream { + return [[self playerForID:stream] stop]; +} + +- (void)pause:(int32_t)stream { + return [[self playerForID:stream] pause]; +} + +- (void)setRate:(float)rate stream:(int32_t)stream { + [self playerForID:stream].rate = rate; +} + +- (void)setVolume:(float)volume stream:(int32_t)stream { + if (volume > 1.0) { + volume = 1.0; + } + + if (volume < 0.0) { + volume = 0.0; + } + + [self playerForID:stream].volume = volume; +} + +- (void)pauseAll { + for (AVAudioPlayer* player in [_players allValues]) { + [player pause]; + } +} + +- (void)resumeAll { + for (AVAudioPlayer* player in [_players allValues]) { + [player play]; + } +} + +- (void)dealloc { + [_players release]; + + [super dealloc]; +} + +@end + +namespace sky { +namespace services { +namespace media { + +SoundPoolImpl::SoundPoolImpl(mojo::InterfaceRequest<::media::SoundPool> request) + : binding_(this, request.Pass()), + sound_pool_([[SoundPoolClient alloc] init]) {} + +SoundPoolImpl::~SoundPoolImpl() { + [sound_pool_ release]; +} + +static base::FilePath TemporaryFilePath() { + char temp[256] = {0}; + NSString* tempDirectory = NSTemporaryDirectory(); + snprintf(temp, sizeof(temp), "%spool.XXXXXX", tempDirectory.UTF8String); + base::FilePath path(mktemp(temp)); + return path; +} + +void SoundPoolImpl::Load(mojo::ScopedDataPipeConsumerHandle data_source, + const ::media::SoundPool::LoadCallback& callback) { + base::mac::ScopedNSAutoreleasePool pool; + + // Copy the contents of the data source to a temporary file + auto path = TemporaryFilePath(); + auto taskRunner = base::MessageLoop::current()->task_runner().get(); + auto copyCallback = base::Bind(&SoundPoolImpl::onCopyToTemp, + base::Unretained(this), callback, path); + + mojo::common::CopyToFile(data_source.Pass(), path, taskRunner, copyCallback); +} + +void SoundPoolImpl::onCopyToTemp( + const ::media::SoundPool::LoadCallback& callback, + base::FilePath path, + bool success) { + base::mac::ScopedNSAutoreleasePool pool; + + if (!success) { + callback.Run(false, 0); + return; + } + + // After the copy, initialize the audio player instance in the pool + NSString* filePath = + [NSString stringWithUTF8String:path.AsUTF8Unsafe().c_str()]; + NSURL* fileURL = [NSURL URLWithString:filePath]; + + int32_t streamID = 0; + BOOL loadResult = [sound_pool_ loadPlayer:fileURL streamOut:&streamID]; + + // Fire user callback + callback.Run(loadResult, loadResult ? streamID : 0); +} + +static float AverageVolume(mojo::Array& channel_volumes) { + // There is no provision to set individual channel volumes. Instead, we just + // average out the volumes and pass that to the sound pool. + size_t volumesCount = channel_volumes.size(); + + if (volumesCount == 0) { + return 1.0; + } + + float sum = 0; + for (const auto& volume : channel_volumes) { + sum += volume; + } + + return sum / volumesCount; +} + +void SoundPoolImpl::Play(int32_t sound_id, + int32_t stream_id, + mojo::Array channel_volumes, + bool loop, + float rate, + const ::media::SoundPool::PlayCallback& callback) { + base::mac::ScopedNSAutoreleasePool pool; + BOOL playResult = [sound_pool_ play:stream_id + volume:AverageVolume(channel_volumes) + loop:loop + rate:rate]; + callback.Run(playResult); +} + +void SoundPoolImpl::Stop(int32_t stream_id) { + base::mac::ScopedNSAutoreleasePool pool; + [sound_pool_ stop:stream_id]; +} + +void SoundPoolImpl::Pause(int32_t stream_id) { + base::mac::ScopedNSAutoreleasePool pool; + [sound_pool_ pause:stream_id]; +} + +void SoundPoolImpl::Resume(int32_t stream_id) { + base::mac::ScopedNSAutoreleasePool pool; + [sound_pool_ resume:stream_id]; +} + +void SoundPoolImpl::SetRate(int32_t stream_id, float rate) { + base::mac::ScopedNSAutoreleasePool pool; + [sound_pool_ setRate:rate stream:stream_id]; +} + +void SoundPoolImpl::SetVolume(int32_t stream_id, + mojo::Array channel_volumes) { + base::mac::ScopedNSAutoreleasePool pool; + [sound_pool_ setVolume:AverageVolume(channel_volumes) stream:stream_id]; +} + +void SoundPoolImpl::PauseAll() { + base::mac::ScopedNSAutoreleasePool pool; + [sound_pool_ pauseAll]; +} + +void SoundPoolImpl::ResumeAll() { + base::mac::ScopedNSAutoreleasePool pool; + [sound_pool_ resumeAll]; +} + +void SoundPoolFactory::Create( + mojo::ApplicationConnection* connection, + mojo::InterfaceRequest<::media::SoundPool> request) { + new SoundPoolImpl(request.Pass()); +} + +} // namespace media +} // namespace services +} // namespace sky