Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,6 @@ build/
*~
*.dat
*.dep

# react native related
node_modules/
76 changes: 76 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
'use strict';
// var Promise = require('promise');

var { DeviceEventEmitter } = require('react-native');
var Streamer = require('NativeModules').RADOUAudioStreamer;
var {EventEmitter} = require('events');

class DouAudio extends EventEmitter {

constructor(id) {
super();
this.id = id;

this.nativeEventSubscription = DeviceEventEmitter.addListener(
"EventAudio-" + this.id,
this._dispatchEvent.bind(this)
);

Object.assign(this, {
bufferingRatio: null,
bytesLoaded: null,
bytesTotal: null,
downloadSpeed: null,

position: null,
duration: null,

// 0 = stopped/uninitialised
// 1 = playing or buffering sound (play has been called, waiting for data etc.)
playState: 0,

// Numeric value indicating a sound's current load status
// 0 = uninitialised
// 1 = loading
// 2 = failed/error
// 3 = loaded/success
readyState: 0,

paused: true
});

for(let command of ['play', 'pause', 'stop', 'setPosition']) {
this[command] = (...args) => {
return Streamer[command].call(this, this.id, ...args);
}
}

}

_dispatchEvent(o) {
console.log(o);
Object.assign(this, o.value);
this.emit(o.name, o.value);
}

destruct() {
// @todo tell the objc to destroy the sound
this.nativeEventSubscription.remove();
Streamer.destruct(this.id);
}

}

DouAudio.createSound = function (options, callback) {
// pollingInterval
Streamer.createSound(options, function (error, id) {
// console.log(arguments);
if(options.autoPlay){
Streamer.play(id);
}
var audio = new DouAudio(id);
callback && callback(audio);
});
}

module.exports = DouAudio;
26 changes: 26 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"name": "DOUAudioStreamer",
"version": "1.0.0",
"description": "DOUAudioStreamer is a Core Audio based streaming audio player for iOS/Mac.",
"main": "index.js",
"directories": {
"example": "example"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "https://github.com/douban/DOUAudioStreamer.git"
},
"author": "Keith Yao <[email protected]> (http://about.me/yaofur)",
"license": "ISC",
"bugs": {
"url": "https://github.com/douban/DOUAudioStreamer/issues"
},
"homepage": "https://github.com/douban/DOUAudioStreamer",
"dependencies": {
"events": "^1.0.2",
"promise": "^7.0.1"
}
}
14 changes: 14 additions & 0 deletions src/RNDouAudioStreamer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//
// SocketBridge.h
// DouAudioStreamer
//
// Created by Keith Yao on 29/05/2015.
// Copyright (c) 2015 Douban. All rights reserved.
//

#import "RCTBridgeModule.h"
#import "RCTLog.h"

@interface RADOUAudioStreamer : NSObject <RCTBridgeModule>

@end
257 changes: 257 additions & 0 deletions src/RNDouAudioStreamer.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,257 @@
//
// SocketBridge.h
// DouAudioStreamer
//
// Created by Keith Yao on 29/05/2015.
// Copyright (c) 2015 Douban. All rights reserved.
//
#import "RNDouAudioStreamer.h"
#import "DOUAudioStreamer.h"
#import "DOUAudioStreamer+Options.h"
#import "RNDouAudioTrack.h"

#import "RCTConvert.h"
#import "RCTBridge.h"
#import "RCTUtils.h"
#import "RCTEventDispatcher.h"

static void *kStatusKVOKey = &kStatusKVOKey;
static void *kDurationKVOKey = &kDurationKVOKey;
static void *kBufferingRatioKVOKey = &kBufferingRatioKVOKey;

@implementation RADOUAudioStreamer {
@private
NSTimer * _timer;
NSMutableDictionary *_sounds;
}

@synthesize bridge = _bridge;

- (instancetype)init {
[DOUAudioStreamer setOptions:[DOUAudioStreamer options] | DOUAudioStreamerRequireSHA256];
_sounds = [NSMutableDictionary dictionaryWithDictionary:@{}];

dispatch_async(
dispatch_get_main_queue(),
// [self methodQueue],
//dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
^{
_timer = [NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector: @selector(_whilePlaying:)
userInfo: nil
repeats: YES];
});

return self;
}

- (DOUAudioStreamer *) getSoundWithName: (NSString *) name
{
return [_sounds objectForKey:name];
}

- (void) createSoundWithName: (NSString *) name andValue: (DOUAudioStreamer *) audio {
[_sounds setObject:audio forKey:name];
}

- (NSString *) uniqueId {
CFUUIDRef uuidRef = CFUUIDCreate(NULL);
CFStringRef uuidStringRef = CFUUIDCreateString(NULL, uuidRef);
CFRelease(uuidRef);
return (__bridge_transfer NSString *)uuidStringRef;
}

- (void) _emitEvent:(NSString *) eventName
withValue: (NSDictionary *) eventValue
andSoundId: (NSString *) soundId
{
NSDictionary * body = @{@"name": eventName,
@"value": eventValue};

NSString * nativeEventName = [NSString stringWithFormat:@"EventAudio-%@", soundId];

NSLog(@"fire event:%@", nativeEventName);

[self.bridge.eventDispatcher
sendDeviceEventWithName:nativeEventName
body:body];
}

- (void) _whilePlaying: (id)timer

{
[_sounds enumerateKeysAndObjectsUsingBlock:^(NSString * soundId, DOUAudioStreamer * streamer, BOOL *stop) {
if(streamer.status != DOUAudioStreamerPlaying) {
return;
}
NSString * eventName = @"whileplaying";
NSDictionary * eventValue = @{
@"position": @([streamer currentTime] * 1000),
@"duration": @([streamer duration] * 1000)
};
[self _emitEvent:eventName withValue:eventValue andSoundId:soundId];
}];
}

RCT_EXPORT_MODULE();

RCT_EXPORT_METHOD(createSound: (NSDictionary *) song
done: (RCTResponseSenderBlock) callback)
{
Track *track = [[Track alloc] init];
[track setArtist:[song objectForKey:@"artist"]];
[track setTitle:[song objectForKey:@"title"]];
[track setAudioFileURL:[NSURL URLWithString:[song objectForKey:@"url"]]];
id streamer = [DOUAudioStreamer streamerWithAudioFile:track];

/*
int oPollingInterval = [[song objectForKey: @"pollingInterval"] doubleValue];
if(oPollingInterval < 1){
// set the default value
oPollingInterval = 1000;
}
NSTimeInterval pollingInterval = oPollingInterval / 1000; */

NSString * audioName = [self uniqueId];
[self createSoundWithName:audioName andValue: streamer];

// add observers
[streamer addObserver:self
forKeyPath:@"status"
options:NSKeyValueObservingOptionNew
context:kStatusKVOKey];

[streamer addObserver:self
forKeyPath:@"duration"
options:NSKeyValueObservingOptionNew
context:kDurationKVOKey];

[streamer addObserver:self
forKeyPath:@"bufferingRatio"
options:NSKeyValueObservingOptionNew
context:kBufferingRatioKVOKey];
// end observers

// @todo add _timer for all playing audios


callback(@[[NSNull null], audioName]);
}

RCT_EXPORT_METHOD(destructSound: (NSString *) name){
DOUAudioStreamer * streamer = [self getSoundWithName:name];
if(streamer == nil){
return;
}
[streamer pause];
[streamer removeObserver:self forKeyPath:@"status"];
[streamer removeObserver:self forKeyPath:@"duration"];
[streamer removeObserver:self forKeyPath:@"bufferingRatio"];
[_sounds removeObjectForKey:name];
}

- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
NSString * eventName;
NSDictionary * eventValue;

DOUAudioStreamer * streamer = object;
NSString * soundId;
NSArray * names = [_sounds allKeysForObject: streamer];
if(names.count >= 1) {
soundId = names[0];
}

if (context == kStatusKVOKey) {
switch (streamer.status) {
case DOUAudioStreamerPlaying:
eventName = @"play";
break;
case DOUAudioStreamerPaused:
eventName = @"pause";
break;
case DOUAudioStreamerIdle:
eventName = @"idle";
break;
case DOUAudioStreamerFinished:
eventName = @"finish";
break;
case DOUAudioStreamerBuffering:
eventName = @"buffering";
break;
case DOUAudioStreamerError:
eventName = @"error";
break;
default:
break;
}
eventValue = @{};
}
else if (context == kDurationKVOKey) {
eventName = @"whileplaying";
eventValue = @{
@"position": @([streamer currentTime]),
@"duration": @([streamer duration])
};
}
else if (context == kBufferingRatioKVOKey) {
eventName = @"whileloading";
eventValue = @{
@"bufferingRatio": @([streamer bufferingRatio]),
@"bytesLoaded": @([streamer receivedLength]),
@"bytesTotal": @([streamer expectedLength]),
@"downloadSpeed": @([streamer downloadSpeed])
};
}
else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
return;
}

NSDictionary * body = @{
@"name": eventName,
@"value": eventValue
};

NSString * nativeEventName = [NSString stringWithFormat:@"EventAudio-%@", soundId];

NSLog(@"fire event:%@", nativeEventName);

[self.bridge.eventDispatcher
sendDeviceEventWithName:nativeEventName
body:body];
}

RCT_EXPORT_METHOD(pause: (NSString *) name){
DOUAudioStreamer * streamer = [self getSoundWithName:name];
if(streamer){
[streamer pause];
}
}

RCT_EXPORT_METHOD(play: (NSString *) name){
DOUAudioStreamer * streamer = [self getSoundWithName:name];
if(streamer){
[streamer play];
}
}

RCT_EXPORT_METHOD(stop: (NSString *) name){
DOUAudioStreamer * streamer = [self getSoundWithName:name];
if(streamer){
[streamer play];
}
}

// NSTimeInterval is double
RCT_EXPORT_METHOD(setPosition: (NSString *) name andTime: (NSTimeInterval) time){
DOUAudioStreamer * streamer = [self getSoundWithName:name];
if(streamer){
[streamer setCurrentTime: time];
}
}
@end
Loading