mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
A sound pool implementation for iOS
This commit is contained in:
parent
2ccac4c983
commit
fd02ee30cb
@ -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 = [
|
||||
|
||||
@ -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);
|
||||
};
|
||||
|
||||
@ -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(
|
||||
|
||||
76
sky/services/media/ios/sound_pool_impl.h
Normal file
76
sky/services/media/ios/sound_pool_impl.h
Normal file
@ -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<float> 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<float> 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_
|
||||
295
sky/services/media/ios/sound_pool_impl.mm
Normal file
295
sky/services/media/ios/sound_pool_impl.mm
Normal file
@ -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 <Foundation/Foundation.h>
|
||||
#include <AVFoundation/AVFoundation.h>
|
||||
|
||||
@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<float>& 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<float> 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<float> 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
|
||||
Loading…
x
Reference in New Issue
Block a user