Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge Edge from perushevandkhmelev #1

Open
wants to merge 13 commits into
base: perushevandkhmelev
Choose a base branch
from
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
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.support.v4.content.ContextCompat;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v4.media.RatingCompat;
import android.support.v4.media.session.PlaybackStateCompat;
Expand All @@ -32,6 +33,7 @@ public class MusicModule extends ReactContextBaseJavaModule implements ServiceCo
private MusicEvents eventHandler;
private ArrayDeque<Runnable> initCallbacks = new ArrayDeque<>();
private boolean connecting = false;
private Bundle options;

public MusicModule(ReactApplicationContext reactContext) {
super(reactContext);
Expand Down Expand Up @@ -65,30 +67,41 @@ public void onCatalystInstanceDestroy() {

@Override
public void onServiceConnected(ComponentName name, IBinder service) {
binder = (MusicBinder)service;
connecting = false;
synchronized(Utils.PLAYBACK_SERVICE_SETUP_LOCK) {
binder = (MusicBinder)service;
connecting = false;

// Triggers all callbacks
while(!initCallbacks.isEmpty()) {
binder.post(initCallbacks.remove());
// Reapply options that user set before with updateOptions
if (this.options != null) {
binder.updateOptions(this.options);
}

// Triggers all callbacks
while(!initCallbacks.isEmpty()) {
binder.post(initCallbacks.remove());
}
}
}

@Override
public void onServiceDisconnected(ComponentName name) {
binder = null;
connecting = false;
synchronized(Utils.PLAYBACK_SERVICE_SETUP_LOCK) {
binder = null;
connecting = false;
}
}

/**
* Waits for a connection to the service and/or runs the {@link Runnable} in the player thread
*/
private void waitForConnection(Runnable r) {
if(binder != null) {
binder.post(r);
return;
} else {
initCallbacks.add(r);
synchronized(Utils.PLAYBACK_SERVICE_SETUP_LOCK) {
if(binder != null) {
binder.post(r);
return;
} else {
initCallbacks.add(r);
}
}

if(connecting) return;
Expand All @@ -97,7 +110,7 @@ private void waitForConnection(Runnable r) {

// Binds the service to get a MediaWrapper instance
Intent intent = new Intent(context, MusicService.class);
context.startService(intent);
ContextCompat.startForegroundService(context, intent);
intent.setAction(Utils.CONNECT_INTENT);
context.bindService(intent, this, 0);

Expand All @@ -113,6 +126,7 @@ public Map<String, Object> getConstants() {

// Capabilities
constants.put("CAPABILITY_PLAY", PlaybackStateCompat.ACTION_PLAY);
constants.put("CAPABILITY_TOGGLE_PLAY_PAUSE", PlaybackStateCompat.ACTION_PLAY_PAUSE);
constants.put("CAPABILITY_PLAY_FROM_ID", PlaybackStateCompat.ACTION_PLAY_FROM_MEDIA_ID);
constants.put("CAPABILITY_PLAY_FROM_SEARCH", PlaybackStateCompat.ACTION_PLAY_FROM_SEARCH);
constants.put("CAPABILITY_PAUSE", PlaybackStateCompat.ACTION_PAUSE);
Expand Down Expand Up @@ -150,12 +164,19 @@ public void setupPlayer(ReadableMap data, final Promise promise) {
waitForConnection(() -> binder.setupPlayer(options, promise));
}

@ReactMethod
public void isServiceRunning(final Promise promise) {
promise.resolve(binder != null);
}

@ReactMethod
public void destroy() {
try {
if(binder != null) {
binder.destroy();
binder = null;
synchronized(Utils.PLAYBACK_SERVICE_SETUP_LOCK) {
if(binder != null) {
binder.destroy();
binder = null;
}
}

ReactContext context = getReactApplicationContext();
Expand All @@ -170,6 +191,9 @@ public void destroy() {
public void updateOptions(ReadableMap data, final Promise callback) {
final Bundle options = Arguments.toBundle(data);

// keep options as we may need them for correct MetadataManager reinitialization later
this.options = options;

waitForConnection(() -> {
binder.updateOptions(options);
callback.resolve(null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@

import android.app.Activity;
import android.app.Notification;
import android.app.NotificationChannel;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.support.v4.app.NotificationCompat;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v4.media.session.MediaButtonReceiver;

import android.support.v4.media.session.MediaSessionCompat;
import com.guichaguri.trackplayer.service.Utils;
import com.facebook.react.HeadlessJsTaskService;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.bridge.Arguments;
Expand Down Expand Up @@ -49,14 +49,16 @@ public void emit(String event, Bundle data) {
}

public void destroy() {
if(handler != null) {
handler.removeMessages(0);
handler = null;
}

if(manager != null) {
manager.destroy();
manager = null;
synchronized(Utils.PLAYBACK_SERVICE_SETUP_LOCK) {
if(handler != null) {
handler.removeMessages(0);
handler = null;
}

if(manager != null) {
manager.destroy();
manager = null;
}
}
}

Expand All @@ -74,20 +76,26 @@ private void onStartForeground() {

// Checks whether there is a React activity
if(reactContext == null || !reactContext.hasCurrentActivity()) {
String channel = null;

if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
channel = NotificationChannel.DEFAULT_CHANNEL_ID;
}

Notification notification = Utils.createBlankSetupNotification(this);
// Sets the service to foreground with an empty notification
startForeground(1, new NotificationCompat.Builder(this, channel).build());
startForeground(1, notification);
// Stops the service right after
stopSelf();
}
}
}

@Override
public void onCreate() {
super.onCreate();
Notification notification = Utils.createBlankSetupNotification(this);
startForeground(1, notification);
synchronized(Utils.PLAYBACK_SERVICE_SETUP_LOCK) {
manager = new MusicManager(this);
handler = new Handler();
}
}

@Nullable
@Override
public IBinder onBind(Intent intent) {
Expand All @@ -111,11 +119,8 @@ public int onStartCommand(Intent intent, int flags, int startId) {
return START_NOT_STICKY;
}

manager = new MusicManager(this);
handler = new Handler();

super.onStartCommand(intent, flags, startId);
return START_STICKY;
return START_NOT_STICKY;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
package com.guichaguri.trackplayer.service;

import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.NotificationCompat;
import android.support.v4.media.RatingCompat;
import android.support.v4.media.session.PlaybackStateCompat;
import com.facebook.react.bridge.Promise;
import com.facebook.react.views.imagehelper.ResourceDrawableIdHelper;
import com.google.android.exoplayer2.upstream.RawResourceDataSource;
import com.guichaguri.trackplayer.R;

/**
* @author Guichaguri
Expand All @@ -19,7 +25,9 @@ public class Utils {
public static final String EVENT_INTENT = "com.guichaguri.trackplayer.event";
public static final String CONNECT_INTENT = "com.guichaguri.trackplayer.connect";
public static final String NOTIFICATION_CHANNEL = "com.guichaguri.trackplayer";
public static final String SETUP_NOTIFICATION_CHANNEL = "com.guichaguri.trackplayer-setup";
public static final String LOG = "RNTrackPlayer";
public static final Object PLAYBACK_SERVICE_SETUP_LOCK = new Object();

public static Runnable toRunnable(Promise promise) {
return () -> promise.resolve(null);
Expand Down Expand Up @@ -149,4 +157,34 @@ public static void setRating(Bundle data, String key, RatingCompat rating) {
}
}

public static void createSetupNotificationChannel(Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(
Utils.SETUP_NOTIFICATION_CHANNEL,
"Playback setup",
NotificationManager.IMPORTANCE_NONE
);
channel.setShowBadge(false);
channel.setSound(null, null);
NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
assert manager != null;
manager.createNotificationChannel(channel);
}
}

public static Notification createBlankSetupNotification(Context context) {
String channel = null;

if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createSetupNotificationChannel(context);
channel = Utils.SETUP_NOTIFICATION_CHANNEL;
}

NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(context, channel);
Notification notification = notificationBuilder.setPriority(NotificationManager.IMPORTANCE_MIN)
.setSmallIcon(R.drawable.play)
.setCategory(Notification.CATEGORY_SERVICE)
.build();
return notification;
}
}
2 changes: 2 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ declare namespace RNTrackPlayer {
export function setupPlayer(options?: PlayerOptions): Promise<void>;
export function destroy(): void;
export function updateOptions(options: MetadataOptions): void;
export function isServiceRunning(): Promise<boolean>;

// Player Queue Commands

Expand Down Expand Up @@ -165,6 +166,7 @@ declare namespace RNTrackPlayer {
export const CAPABILITY_PLAY_FROM_ID: Capability;
export const CAPABILITY_PLAY_FROM_SEARCH: Capability;
export const CAPABILITY_PAUSE: Capability;
export const CAPABILITY_TOGGLE_PLAY_PAUSE: Capability;
export const CAPABILITY_STOP: Capability;
export const CAPABILITY_SEEK_TO: Capability;
export const CAPABILITY_SKIP: Capability;
Expand Down
4 changes: 3 additions & 1 deletion ios/RNTrackPlayer/Models/Capabilities.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import Foundation

enum Capability: String {
case play, pause, stop, next, previous, jumpForward, jumpBackward, seek
case play, pause, togglePlayPause, stop, next, previous, jumpForward, jumpBackward, seek

func mapToPlayerCommand(jumpInterval: NSNumber?) -> RemoteCommand {
switch self {
Expand All @@ -19,6 +19,8 @@ enum Capability: String {
return .play
case .pause:
return .pause
case .togglePlayPause:
return .togglePlayPause
case .next:
return .next
case .previous:
Expand Down
9 changes: 8 additions & 1 deletion ios/RNTrackPlayer/RNTrackPlayer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ public class RNTrackPlayer: RCTEventEmitter, AudioPlayerDelegate {
"CAPABILITY_PLAY_FROM_ID": "NOOP",
"CAPABILITY_PLAY_FROM_SEARCH": "NOOP",
"CAPABILITY_PAUSE": Capability.pause.rawValue,
"CAPABILITY_TOGGLE_PLAY_PAUSE": Capability.togglePlayPause.rawValue,
"CAPABILITY_STOP": Capability.stop.rawValue,
"CAPABILITY_SEEK_TO": Capability.seek.rawValue,
"CAPABILITY_SKIP": "NOOP",
Expand Down Expand Up @@ -155,6 +156,12 @@ public class RNTrackPlayer: RCTEventEmitter, AudioPlayerDelegate {

resolve(NSNull())
}

@objc(isServiceRunning:rejecter:)
public func isServiceRunning(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
// TODO That is probably always true
resolve(player != nil)
}

@objc(destroy)
public func destroy() {
Expand Down Expand Up @@ -453,7 +460,7 @@ public class RNTrackPlayer: RCTEventEmitter, AudioPlayerDelegate {

@objc(getBufferedPosition:rejecter:)
public func getBufferedPosition(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
resolve(0)
resolve(player.bufferedPosition)
}

@objc(getPosition:rejecter:)
Expand Down
3 changes: 3 additions & 0 deletions ios/RNTrackPlayer/RNTrackPlayerBridge.m
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ @interface RCT_EXTERN_REMAP_MODULE(TrackPlayerModule, RNTrackPlayer, NSObject)
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject);

RCT_EXTERN_METHOD(isServiceRunning:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject);

RCT_EXTERN_METHOD(destroy);

RCT_EXTERN_METHOD(updateOptions:(NSDictionary *)options
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@ class AVPlayerWrapper: AVPlayerWrapperProtocol {
return 0.0
}

var bufferedPosition: TimeInterval {
return currentItem?.loadedTimeRanges.last?.timeRangeValue.end.seconds ?? 0
}

weak var delegate: AVPlayerWrapperDelegate? = nil

var bufferDuration: TimeInterval = 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import Foundation
import AVFoundation


protocol AVPlayerWrapperProtocol {
protocol AVPlayerWrapperProtocol: class {

var state: AVPlayerWrapperState { get }

Expand All @@ -19,6 +19,8 @@ protocol AVPlayerWrapperProtocol {

var duration: TimeInterval { get }

var bufferedPosition: TimeInterval { get }

var reasonForWaitingToPlay: AVPlayer.WaitingReason? { get }


Expand Down
7 changes: 7 additions & 0 deletions ios/RNTrackPlayer/Vendor/AudioPlayer/AudioPlayer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,13 @@ public class AudioPlayer: AVPlayerWrapperDelegate {
return wrapper.duration
}

/**
The bufferedPosition of the current AudioItem.
*/
public var bufferedPosition: Double {
return wrapper.bufferedPosition
}

/**
The current state of the underlying `AudioPlayer`.
*/
Expand Down
Loading