├── .gitattributes ├── deliver.sh ├── android ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── com │ │ └── reactlibrary │ │ ├── RNNowPlayingPackage.java │ │ └── RNNowPlayingModule.java ├── build.gradle ├── gradlew.bat └── gradlew ├── .project ├── .gitignore ├── package.json ├── ios ├── RNNowPlaying.podspec ├── RNNowPlaying.h ├── RNNowPlaying.m └── RNNowPlaying.xcodeproj │ └── project.pbxproj ├── README.md └── index.js /.gitattributes: -------------------------------------------------------------------------------- 1 | *.pbxproj -text -------------------------------------------------------------------------------- /deliver.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | git add . 3 | git commit -m "$2" 4 | npm version $1 5 | npm publish 6 | git push --tags 7 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weswes/react-native-now-playing/HEAD/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | Librairie nowplaying 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Jun 14 17:53:57 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip 7 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | apply plugin: 'com.android.library' 3 | 4 | android { 5 | compileSdkVersion 23 6 | buildToolsVersion "23.0.1" 7 | 8 | defaultConfig { 9 | minSdkVersion 16 10 | targetSdkVersion 22 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | lintOptions { 15 | abortOnError false 16 | } 17 | } 18 | 19 | repositories { 20 | mavenCentral() 21 | } 22 | 23 | dependencies { 24 | compile 'com.facebook.react:react-native:+' 25 | } 26 | 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # OSX 3 | # 4 | .DS_Store 5 | 6 | # node.js 7 | # 8 | node_modules/ 9 | npm-debug.log 10 | yarn-error.log 11 | 12 | 13 | # Xcode 14 | # 15 | build/ 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | xcuserdata 25 | *.xccheckout 26 | *.moved-aside 27 | DerivedData 28 | *.hmap 29 | *.ipa 30 | *.xcuserstate 31 | project.xcworkspace 32 | 33 | 34 | # Android/IntelliJ 35 | # 36 | build/ 37 | .idea 38 | .gradle 39 | local.properties 40 | *.iml 41 | 42 | # BUCK 43 | buck-out/ 44 | \.buckd/ 45 | *.keystore 46 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-now-playing", 3 | "version": "0.2.15", 4 | "description": "## Getting started", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [ 10 | "react-native" 11 | ], 12 | "author": "Wesley Anest", 13 | "license": "ISC", 14 | "peerDependencies": { 15 | "react-native": "^0.41.2" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/weswes/react-native-now-playing.git" 20 | }, 21 | "bugs": { 22 | "url": "https://github.com/weswes/react-native-now-playing/issues" 23 | }, 24 | "homepage": "https://github.com/weswes/react-native-now-playing#readme" 25 | } 26 | -------------------------------------------------------------------------------- /ios/RNNowPlaying.podspec: -------------------------------------------------------------------------------- 1 | 2 | Pod::Spec.new do |s| 3 | s.name = "RNNowPlaying" 4 | s.version = "1.0.0" 5 | s.summary = "RNNowPlaying" 6 | s.description = <<-DESC 7 | RNNowPlaying 8 | DESC 9 | s.homepage = "https://github.com/author/RNNowPlaying.git" 10 | s.license = "MIT" 11 | # s.license = { :type => "MIT", :file => "FILE_LICENSE" } 12 | s.author = { "author" => "author@domain.cn" } 13 | s.platform = :ios, "7.0" 14 | s.source = { :git => "https://github.com/weswes/react-native-now-playing.git", :tag => "master" } 15 | s.source_files = "**/*.{h,m}" 16 | s.public_header_files = "**/*.{h}" 17 | s.requires_arc = true 18 | 19 | s.dependency "React" 20 | s.dependency "SpotifyAppRemoteSDK" 21 | 22 | end 23 | -------------------------------------------------------------------------------- /ios/RNNowPlaying.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import 4 | 5 | #import 6 | 7 | #if __has_include() 8 | #import 9 | 10 | 11 | @interface RNNowPlaying : RCTEventEmitter 12 | 13 | + (_Nonnull instancetype)instance; 14 | 15 | @property (nonatomic, retain) MPMusicPlayerController *musicPlayer; 16 | @property (nonatomic, strong) SPTAppRemote *appRemote; // Spotify App Remote 17 | 18 | - (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary *)options; 19 | 20 | 21 | #else 22 | 23 | @interface RNNowPlaying : RCTEventEmitter 24 | 25 | @property (nonatomic, retain) MPMusicPlayerController *musicPlayer; 26 | 27 | #endif 28 | 29 | @end 30 | 31 | -------------------------------------------------------------------------------- /android/src/main/java/com/reactlibrary/RNNowPlayingPackage.java: -------------------------------------------------------------------------------- 1 | package com.reactlibrary; 2 | 3 | import com.facebook.react.ReactPackage; 4 | import com.facebook.react.bridge.JavaScriptModule; 5 | import com.facebook.react.bridge.NativeModule; 6 | import com.facebook.react.bridge.ReactApplicationContext; 7 | import com.facebook.react.uimanager.ViewManager; 8 | 9 | import java.util.Arrays; 10 | import java.util.Collections; 11 | import java.util.List; 12 | 13 | public class RNNowPlayingPackage implements ReactPackage { 14 | 15 | @Override 16 | public List createNativeModules(ReactApplicationContext reactContext) { 17 | return Arrays.asList(new RNNowPlayingModule(reactContext)); 18 | } 19 | 20 | public List> createJSModules() { 21 | return Collections.emptyList(); 22 | } 23 | 24 | @Override 25 | public List createViewManagers(ReactApplicationContext reactContext) { 26 | return Collections.emptyList(); 27 | } 28 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-native-now-playing 2 | React Native module to detect Now Playing Music from various player. Compatible with iOS & Android. 3 | 4 | 5 | | Player | iOS | Android | 6 | | --- | --- | --- | 7 | | Spotify | :x: | :heavy_check_mark: | 8 | | Deezer | :x: | :question: | 9 | | Apple Music | :heavy_check_mark: | :question: | 10 | | Google Music | :x: | :heavy_check_mark: | 11 | | VLC | :x: | :question: | 12 | 13 | ## Getting started 14 | 15 | `$ npm install react-native-now-playing --save` 16 | 17 | ### Mostly automatic installation 18 | 19 | `$ react-native link react-native-now-playing` 20 | 21 | ### Podfile 22 | 23 | Add pod 'SpotifyAppRemoteSDK' into your podfile 24 | 25 | ### Manual installation 26 | 27 | #### iOS 28 | 29 | 1. In XCode, in the project navigator, right click `Libraries` ➜ `Add Files to [your project's name]` 30 | 2. Go to `node_modules` ➜ `react-native-now-playing` and add `RNNowPlaying.xcodeproj` 31 | 3. In XCode, in the project navigator, select your project. Add `libRNNowPlaying.a` to your project's `Build Phases` ➜ `Link Binary With Libraries` 32 | 4. Run your project (`Cmd+R`)< 33 | 34 | #### Android 35 | 36 | 1. Open up `android/app/src/main/java/[...]/MainActivity.java` 37 | 38 | * Add `import com.reactlibrary.RNNowPlayingPackage;` to the imports at the top of the file 39 | * Add `new RNNowPlayingPackage()` to the list returned by the `getPackages()` method 40 | 41 | 2. Append the following lines to `android/settings.gradle`: 42 | ``` 43 | include ':react-native-now-playing' 44 | project(':react-native-now-playing').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-now-playing/android') 45 | ``` 46 | 3. Insert the following lines inside the dependencies block in `android/app/build.gradle`: 47 | ``` 48 | compile project(':react-native-now-playing') 49 | ``` 50 | 51 | ## Usage 52 | 53 | ```javascript 54 | import RNNowPlaying from "react-native-now-playing"; 55 | 56 | // TODO: What to do with the module? 57 | RNNowPlaying; 58 | ``` 59 | -------------------------------------------------------------------------------- /android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import { NativeEventEmitter, NativeModules, Platform } from 'react-native'; 2 | 3 | const RNNowPlaying = NativeModules.RNNowPlaying; 4 | const NowPlayingEventEmitter = new NativeEventEmitter(RNNowPlaying); 5 | 6 | const fetchDeezerData = (deezerUserId) => 7 | fetch("http://www.deezer.com/profile/" + deezerUserId) 8 | .then(response => { 9 | var html = response.text()["_55"]; 10 | var regexp = new RegExp(/{("online":{).*?(?=,"ALB_PICTURE")/g); 11 | let res = regexp.exec(html)[0] + "}}"; 12 | res = JSON.parse(res); 13 | return { 14 | title: res.online.SNG_TITLE, 15 | artist: res.online.ART_NAME, 16 | albumTitle: res.online.ALB_TITLE 17 | }; 18 | }) 19 | .catch(error => { 20 | console.log("API getDeezer error:" + error); 21 | }); 22 | 23 | class NowPlaying { 24 | /** 25 | Begin observing for music events 26 | Android: 27 | - Register to 'all' music players intent in Native Module 28 | iOS: 29 | - Register to Apple music events in Native Module 30 | - Auth and register Spotify events in Native Module if installed 31 | - Auth and fetch every 60sec Deezer now playing song 32 | **/ 33 | startObserving(eventCallback) { 34 | console.log("NowPlaying startObserving"); 35 | this.eventCallback = eventCallback; 36 | RNNowPlaying.startObserving(); 37 | // Suscribe to Native Module events 38 | this.listener = NowPlayingEventEmitter.addListener( 39 | "NowPlayingEvent", 40 | this.eventCallback 41 | ); 42 | if (Platform.OS == "ios") 43 | this.authDeezer(); 44 | } 45 | 46 | stopObserving() { 47 | console.log("NowPlaying stopObserving"); 48 | this.listener.remove(); 49 | //TODO RNNowPlaying.stopObserving(); 50 | if (Platform.OS == "ios") 51 | clearInterval(this.deezerInterval); 52 | } 53 | 54 | ////////////////////////////////////////////////////////// 55 | // DEEZER: for iOS only // 56 | ////////////////////////////////////////////////////////// 57 | 58 | /** 59 | Auth with deezer and retrieve deezer User Id 60 | Then call fetchDeezerData every 60sec 61 | **/ 62 | authDeezer(){ 63 | console.log("NowPlaying authDeezer"); 64 | // Tester si token existe 65 | const APP_ID = "215504"; // 66 | const REDIRECT_URI = ""; 67 | const authUrl = "https://connect.deezer.com/oauth/auth.php?app_id="+APP_ID+"&redirect_uri="+REDIRECT_URI+"&perms=basic_access,offline_access&response_type=token"; 68 | // Fetch will open browser and then redirect user to the app 69 | fetch(authUrl) 70 | .then(response => { 71 | 72 | }) 73 | .catch(error => { 74 | console.log("Auth Deezer error:" + error); 75 | }); 76 | 77 | 78 | this.deezerUserId = ""; 79 | this.deezerInterval = setInterval(fetchDeezerData, 60000); 80 | } 81 | 82 | 83 | authDeezerCallback(){ 84 | //save token 85 | console.log(response); 86 | token = ""; // save token as it never expire 87 | fetch("https://api.deezer.com/user/me?token"+token) 88 | .then(response => { 89 | console.log(response); 90 | user = ""; 91 | }) 92 | } 93 | 94 | fetchDeezerData(){ 95 | console.log("NowPlaying fetchDeezerData"); 96 | var deezerNowPlaying = fetchDeezerData(this.deezerUserId); 97 | this.eventCallback(deezerNowPlaying.title, deezerNowPlaying.albumTitle, deezerNowPlaying.artist, "iOS", "Deezer"); 98 | } 99 | 100 | } 101 | 102 | export default new NowPlaying(); 103 | -------------------------------------------------------------------------------- /android/src/main/java/com/reactlibrary/RNNowPlayingModule.java: -------------------------------------------------------------------------------- 1 | package com.reactlibrary; 2 | 3 | import android.content.BroadcastReceiver; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.content.IntentFilter; 7 | import android.media.AudioManager; 8 | import android.support.annotation.Nullable; 9 | import android.util.Log; 10 | 11 | import com.facebook.react.bridge.Arguments; 12 | import com.facebook.react.bridge.ReactApplicationContext; 13 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 14 | import com.facebook.react.bridge.WritableMap; 15 | import com.facebook.react.modules.core.DeviceEventManagerModule; 16 | 17 | public class RNNowPlayingModule extends ReactContextBaseJavaModule { 18 | public static final int REQUEST_CODE_READ_STORAGE = 180; 19 | private Context context; 20 | private final ReactApplicationContext reactContext; 21 | 22 | static final class BroadcastTypes { 23 | static final String ANDROID_PLAYSTATE_CHANGED = "com.android.music.playstatechanged"; 24 | static final String ANDROID_METADATA_CHANGED = "com.android.music.metadatachanged"; 25 | static final String SPOTIFY_METADATA_CHANGED = "com.spotify.music.metadatachanged"; 26 | static final String SPOTIFY_PLAYBACK_STATE_CHANGED = "com.spotify.music.playbackstatechanged"; 27 | } 28 | 29 | public RNNowPlayingModule(ReactApplicationContext reactContext) { 30 | super(reactContext); 31 | this.reactContext = reactContext; 32 | this.registerListenedMusic(reactContext); 33 | } 34 | 35 | @Override 36 | public String getName() { 37 | return "RNNowPlaying"; 38 | } 39 | 40 | private void sendEvent(String eventName, @Nullable WritableMap params){ 41 | Log.v("intent", "send event"); 42 | try{ 43 | if (getReactApplicationContext().hasActiveCatalystInstance()) { 44 | Log.v("intent", "send event hasActiveCatalystInstance ok"); 45 | getReactApplicationContext().getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) 46 | .emit(eventName, params); 47 | Log.v("intent", "send event ok"); 48 | } 49 | } catch (Exception ex){ 50 | Log.v("intent", "send event ko thread interrupt"); 51 | } 52 | 53 | } 54 | 55 | public void registerListenedMusic(Context context) { 56 | IntentFilter iF = new IntentFilter(); 57 | iF.addAction(BroadcastTypes.ANDROID_PLAYSTATE_CHANGED); 58 | iF.addAction(BroadcastTypes.ANDROID_METADATA_CHANGED); 59 | iF.addAction(BroadcastTypes.SPOTIFY_METADATA_CHANGED); 60 | iF.addAction(BroadcastTypes.SPOTIFY_PLAYBACK_STATE_CHANGED); 61 | 62 | this.context = context; 63 | context.registerReceiver(mReceiver, iF); 64 | } 65 | 66 | 67 | private BroadcastReceiver mReceiver; 68 | { 69 | mReceiver = new BroadcastReceiver() { 70 | @Override 71 | public void onReceive(Context context, Intent intent) { 72 | 73 | String action = intent.getAction(); 74 | String cmd = intent.getStringExtra("command"); 75 | String artist = intent.getStringExtra("artist"); 76 | String album = intent.getStringExtra("album"); 77 | String track = intent.getStringExtra("track"); 78 | Long duration = intent.getLongExtra("duration", 0); 79 | Boolean playing = intent.getBooleanExtra("playing", false); 80 | 81 | 82 | if (artist == null && album == null && track == null){ 83 | return; 84 | } 85 | 86 | AudioManager manager = (AudioManager)(context).getSystemService(Context.AUDIO_SERVICE); 87 | 88 | Log.v("intent", "ecoute locale detectée ac:" + action + " cmd:" + cmd + " track " + track + " playing " + playing + "music active: " + manager.isMusicActive()); 89 | 90 | if (playing){ 91 | WritableMap params = Arguments.createMap(); 92 | params.putString("title", track); 93 | params.putString("artist", artist); 94 | params.putString("albumTitle", album); 95 | sendEvent("NowPlayingEvent", params); 96 | } 97 | } 98 | }; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /android/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /ios/RNNowPlaying.m: -------------------------------------------------------------------------------- 1 | #import "RNNowPlaying.h" 2 | 3 | @implementation RNNowPlaying 4 | { 5 | bool hasListeners; 6 | } 7 | 8 | RCT_EXPORT_MODULE(); 9 | 10 | -(BOOL)requiresMainQueueSetup 11 | { 12 | return YES; 13 | } 14 | 15 | static RNNowPlaying *RNNowPlayingInstance = nil; 16 | 17 | + (nonnull instancetype)instance { 18 | return RNNowPlayingInstance; 19 | } 20 | 21 | - (NSArray *)supportedEvents 22 | { 23 | return @[@"NowPlayingEvent"]; 24 | } 25 | 26 | /** 27 | Register to events 28 | **/ 29 | -(void)startObserving{ 30 | NSLog(@"RNNowPlaying startObserving"); 31 | // Check Authorization on MPMediaLibrary 32 | // Traiter les cas du refus... 33 | if ([MPMediaLibrary authorizationStatus] == MPMediaLibraryAuthorizationStatusAuthorized){ 34 | [self registerNotif]; 35 | } else { 36 | [MPMediaLibrary requestAuthorization:^(MPMediaLibraryAuthorizationStatus status) { 37 | [self registerNotif]; 38 | }]; 39 | } 40 | 41 | #if __has_include() 42 | NSLog(@"Spotify Framework is installed !"); 43 | [self startObservingSpotify]; 44 | #endif 45 | 46 | } 47 | 48 | -(void)registerNotif{ 49 | hasListeners = YES; 50 | self.musicPlayer = [MPMusicPlayerController systemMusicPlayer]; 51 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(nowPlayingEventReceived:) name:MPMusicPlayerControllerNowPlayingItemDidChangeNotification object:self.musicPlayer]; 52 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(nowPlayingEventReceived:) name:MPMusicPlayerControllerPlaybackStateDidChangeNotification object:self.musicPlayer]; 53 | [self.musicPlayer beginGeneratingPlaybackNotifications]; 54 | } 55 | 56 | - (void)nowPlayingEventReceived:(NSNotification *)notification 57 | { 58 | //e.playbackTime, e.playbackDuration, e.title, e.albumTitle, e.artist 59 | MPMusicPlayerController *player = (MPMusicPlayerController *)notification.object; 60 | MPMediaItem *item = [player nowPlayingItem]; 61 | 62 | 63 | NSString *playbackTime = [NSString stringWithFormat:@"%@", [NSNumber numberWithDouble:player.currentPlaybackTime]]; 64 | NSString *playbackDuration = [NSString stringWithFormat:@"%@", [NSNumber numberWithDouble:item.playbackDuration]]; 65 | NSString *artist = [NSString stringWithFormat:@"%@", item.artist]; 66 | NSString *albumTitle = [NSString stringWithFormat:@"%@", item.albumTitle]; 67 | NSString *title = [NSString stringWithFormat:@"%@", item.title]; 68 | 69 | NSLog(@"[Nowplaying] pbstate: %ld", (long)player.playbackState); 70 | NSLog(@"[Nowplaying] artist: %@", artist); 71 | NSLog(@"[Nowplaying] albumTitle: %@", albumTitle); 72 | NSLog(@"[Nowplaying] title: %@", title); 73 | 74 | if (player.playbackState == MPMusicPlaybackStatePlaying 75 | && ![artist isEqualToString:@"(null)"] 76 | && ![albumTitle isEqualToString:@"(null)"] 77 | && ![title isEqualToString:@"(null)"] 78 | ){ 79 | [self sendEventWithName:@"NowPlayingEvent" 80 | body:@{@"playbackTime": playbackTime, 81 | @"playbackDuration": playbackDuration, 82 | @"title": title, 83 | @"albumTitle": albumTitle, 84 | @"artist": artist 85 | }]; 86 | } 87 | 88 | } 89 | 90 | 91 | #if __has_include() 92 | -(void)startObservingSpotify{ 93 | SPTAppRemoteConnectionParamsImageFormat format = SPTAppRemoteConnectionParamsImageFormatAny; 94 | SPTAppRemoteConnectionParams *params = 95 | [[SPTAppRemoteConnectionParams alloc] initWithClientIdentifier:@"e5dec3e3ce1643909db47c3d396861c8" 96 | redirectURI:@"spotifywatchexample://" 97 | name:@"Kizbee" 98 | accessToken:nil 99 | defaultImageSize:CGSizeZero 100 | imageFormat:format]; 101 | 102 | self.appRemote = [[SPTAppRemote alloc] initWithConnectionParameters:params 103 | logLevel:SPTAppRemoteLogLevelDebug]; 104 | self.appRemote.delegate = self; 105 | 106 | [self.appRemote connect]; 107 | 108 | [self startObserving]; 109 | } 110 | 111 | 112 | - (void)appRemote:(SPTAppRemote *)appRemote didFailConnectionAttemptWithError:(NSError *)error 113 | { 114 | // Connection failed 115 | NSLog(@"Connection failed %@", error); 116 | BOOL spotifyInstalled = [self.appRemote authorizeAndPlayURI:@""]; 117 | if (!spotifyInstalled) { 118 | /* 119 | * The Spotify app is not installed. 120 | * Use SKStoreProductViewController with [SPTAppRemote spotifyItunesItemIdentifier] to present the user 121 | * with a way to install the Spotify app. 122 | */ 123 | } 124 | } 125 | 126 | - (void)appRemote:(SPTAppRemote *)appRemote didDisconnectWithError:(nullable NSError *)error 127 | { 128 | // Connection disconnected 129 | NSLog(@"Connection disconnected %@", error); 130 | } 131 | 132 | - (void)playerStateDidChange:(id)playerState 133 | { 134 | if (![playerState.track.name isEqualToString:@"Spotify"] && !playerState.isPaused){ 135 | NSString *playbackTime = [NSString stringWithFormat:@"%@", [NSNumber numberWithDouble:playerState.playbackPosition]]; 136 | NSString *playbackDuration = [NSString stringWithFormat:@"%@", [NSNumber numberWithDouble:playerState.track.duration]]; 137 | NSString *artist = [NSString stringWithFormat:@"%@", playerState.track.artist.name]; 138 | NSString *albumTitle = [NSString stringWithFormat:@"%@", playerState.track.album.name]; 139 | NSString *title = [NSString stringWithFormat:@"%@", playerState.track.name]; 140 | 141 | NSLog(@"[Nowplaying] artist: %@", artist); 142 | NSLog(@"[Nowplaying] albumTitle: %@", albumTitle); 143 | NSLog(@"[Nowplaying] title: %@", title); 144 | } 145 | 146 | } 147 | 148 | - (void)appRemoteDidEstablishConnection:(SPTAppRemote *)appRemote 149 | { 150 | // Connection was successful, you can begin issuing commands 151 | 152 | appRemote.playerAPI.delegate = self; 153 | [appRemote.playerAPI subscribeToPlayerState: 154 | ^(id _Nullable result, NSError * _Nullable error) { 155 | if (error) { 156 | NSLog(@"%@", error.localizedDescription); 157 | } 158 | }]; 159 | } 160 | 161 | - (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary *)options 162 | { 163 | NSDictionary *params = [self.appRemote authorizationParametersFromURL:url]; 164 | NSString *token = params[SPTAppRemoteAccessTokenKey]; 165 | if (token) { 166 | self.appRemote.connectionParameters.accessToken = token; 167 | } else if (params[SPTAppRemoteErrorDescriptionKey]) { 168 | NSLog(@"%@", params[SPTAppRemoteErrorDescriptionKey]); 169 | } 170 | return YES; 171 | } 172 | #endif 173 | 174 | @end 175 | -------------------------------------------------------------------------------- /ios/RNNowPlaying.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | B3E7B58A1CC2AC0600A0062D /* RNNowPlaying.m in Sources */ = {isa = PBXBuildFile; fileRef = B3E7B5891CC2AC0600A0062D /* RNNowPlaying.m */; }; 11 | /* End PBXBuildFile section */ 12 | 13 | /* Begin PBXCopyFilesBuildPhase section */ 14 | 58B511D91A9E6C8500147676 /* CopyFiles */ = { 15 | isa = PBXCopyFilesBuildPhase; 16 | buildActionMask = 2147483647; 17 | dstPath = "include/$(PRODUCT_NAME)"; 18 | dstSubfolderSpec = 16; 19 | files = ( 20 | ); 21 | runOnlyForDeploymentPostprocessing = 0; 22 | }; 23 | /* End PBXCopyFilesBuildPhase section */ 24 | 25 | /* Begin PBXFileReference section */ 26 | 134814201AA4EA6300B7C361 /* libRNNowPlaying.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRNNowPlaying.a; sourceTree = BUILT_PRODUCTS_DIR; }; 27 | B3E7B5881CC2AC0600A0062D /* RNNowPlaying.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNNowPlaying.h; sourceTree = ""; }; 28 | B3E7B5891CC2AC0600A0062D /* RNNowPlaying.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNNowPlaying.m; sourceTree = ""; }; 29 | /* End PBXFileReference section */ 30 | 31 | /* Begin PBXFrameworksBuildPhase section */ 32 | 58B511D81A9E6C8500147676 /* Frameworks */ = { 33 | isa = PBXFrameworksBuildPhase; 34 | buildActionMask = 2147483647; 35 | files = ( 36 | ); 37 | runOnlyForDeploymentPostprocessing = 0; 38 | }; 39 | /* End PBXFrameworksBuildPhase section */ 40 | 41 | /* Begin PBXGroup section */ 42 | 134814211AA4EA7D00B7C361 /* Products */ = { 43 | isa = PBXGroup; 44 | children = ( 45 | 134814201AA4EA6300B7C361 /* libRNNowPlaying.a */, 46 | ); 47 | name = Products; 48 | sourceTree = ""; 49 | }; 50 | 58B511D21A9E6C8500147676 = { 51 | isa = PBXGroup; 52 | children = ( 53 | B3E7B5881CC2AC0600A0062D /* RNNowPlaying.h */, 54 | B3E7B5891CC2AC0600A0062D /* RNNowPlaying.m */, 55 | 134814211AA4EA7D00B7C361 /* Products */, 56 | ); 57 | sourceTree = ""; 58 | }; 59 | /* End PBXGroup section */ 60 | 61 | /* Begin PBXNativeTarget section */ 62 | 58B511DA1A9E6C8500147676 /* RNNowPlaying */ = { 63 | isa = PBXNativeTarget; 64 | buildConfigurationList = 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RNNowPlaying" */; 65 | buildPhases = ( 66 | 58B511D71A9E6C8500147676 /* Sources */, 67 | 58B511D81A9E6C8500147676 /* Frameworks */, 68 | 58B511D91A9E6C8500147676 /* CopyFiles */, 69 | ); 70 | buildRules = ( 71 | ); 72 | dependencies = ( 73 | ); 74 | name = RNNowPlaying; 75 | productName = RCTDataManager; 76 | productReference = 134814201AA4EA6300B7C361 /* libRNNowPlaying.a */; 77 | productType = "com.apple.product-type.library.static"; 78 | }; 79 | /* End PBXNativeTarget section */ 80 | 81 | /* Begin PBXProject section */ 82 | 58B511D31A9E6C8500147676 /* Project object */ = { 83 | isa = PBXProject; 84 | attributes = { 85 | LastUpgradeCheck = 0610; 86 | ORGANIZATIONNAME = Facebook; 87 | TargetAttributes = { 88 | 58B511DA1A9E6C8500147676 = { 89 | CreatedOnToolsVersion = 6.1.1; 90 | }; 91 | }; 92 | }; 93 | buildConfigurationList = 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "RNNowPlaying" */; 94 | compatibilityVersion = "Xcode 3.2"; 95 | developmentRegion = English; 96 | hasScannedForEncodings = 0; 97 | knownRegions = ( 98 | en, 99 | ); 100 | mainGroup = 58B511D21A9E6C8500147676; 101 | productRefGroup = 58B511D21A9E6C8500147676; 102 | projectDirPath = ""; 103 | projectRoot = ""; 104 | targets = ( 105 | 58B511DA1A9E6C8500147676 /* RNNowPlaying */, 106 | ); 107 | }; 108 | /* End PBXProject section */ 109 | 110 | /* Begin PBXSourcesBuildPhase section */ 111 | 58B511D71A9E6C8500147676 /* Sources */ = { 112 | isa = PBXSourcesBuildPhase; 113 | buildActionMask = 2147483647; 114 | files = ( 115 | B3E7B58A1CC2AC0600A0062D /* RNNowPlaying.m in Sources */, 116 | ); 117 | runOnlyForDeploymentPostprocessing = 0; 118 | }; 119 | /* End PBXSourcesBuildPhase section */ 120 | 121 | /* Begin XCBuildConfiguration section */ 122 | 58B511ED1A9E6C8500147676 /* Debug */ = { 123 | isa = XCBuildConfiguration; 124 | buildSettings = { 125 | ALWAYS_SEARCH_USER_PATHS = NO; 126 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 127 | CLANG_CXX_LIBRARY = "libc++"; 128 | CLANG_ENABLE_MODULES = YES; 129 | CLANG_ENABLE_OBJC_ARC = YES; 130 | CLANG_WARN_BOOL_CONVERSION = YES; 131 | CLANG_WARN_CONSTANT_CONVERSION = YES; 132 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 133 | CLANG_WARN_EMPTY_BODY = YES; 134 | CLANG_WARN_ENUM_CONVERSION = YES; 135 | CLANG_WARN_INT_CONVERSION = YES; 136 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 137 | CLANG_WARN_UNREACHABLE_CODE = YES; 138 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 139 | COPY_PHASE_STRIP = NO; 140 | ENABLE_STRICT_OBJC_MSGSEND = YES; 141 | GCC_C_LANGUAGE_STANDARD = gnu99; 142 | GCC_DYNAMIC_NO_PIC = NO; 143 | GCC_OPTIMIZATION_LEVEL = 0; 144 | GCC_PREPROCESSOR_DEFINITIONS = ( 145 | "DEBUG=1", 146 | "$(inherited)", 147 | ); 148 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 149 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 150 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 151 | GCC_WARN_UNDECLARED_SELECTOR = YES; 152 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 153 | GCC_WARN_UNUSED_FUNCTION = YES; 154 | GCC_WARN_UNUSED_VARIABLE = YES; 155 | IPHONEOS_DEPLOYMENT_TARGET = 7.0; 156 | MTL_ENABLE_DEBUG_INFO = YES; 157 | ONLY_ACTIVE_ARCH = YES; 158 | SDKROOT = iphoneos; 159 | }; 160 | name = Debug; 161 | }; 162 | 58B511EE1A9E6C8500147676 /* Release */ = { 163 | isa = XCBuildConfiguration; 164 | buildSettings = { 165 | ALWAYS_SEARCH_USER_PATHS = NO; 166 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 167 | CLANG_CXX_LIBRARY = "libc++"; 168 | CLANG_ENABLE_MODULES = YES; 169 | CLANG_ENABLE_OBJC_ARC = YES; 170 | CLANG_WARN_BOOL_CONVERSION = YES; 171 | CLANG_WARN_CONSTANT_CONVERSION = YES; 172 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 173 | CLANG_WARN_EMPTY_BODY = YES; 174 | CLANG_WARN_ENUM_CONVERSION = YES; 175 | CLANG_WARN_INT_CONVERSION = YES; 176 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 177 | CLANG_WARN_UNREACHABLE_CODE = YES; 178 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 179 | COPY_PHASE_STRIP = YES; 180 | ENABLE_NS_ASSERTIONS = NO; 181 | ENABLE_STRICT_OBJC_MSGSEND = YES; 182 | GCC_C_LANGUAGE_STANDARD = gnu99; 183 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 184 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 185 | GCC_WARN_UNDECLARED_SELECTOR = YES; 186 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 187 | GCC_WARN_UNUSED_FUNCTION = YES; 188 | GCC_WARN_UNUSED_VARIABLE = YES; 189 | IPHONEOS_DEPLOYMENT_TARGET = 7.0; 190 | MTL_ENABLE_DEBUG_INFO = NO; 191 | SDKROOT = iphoneos; 192 | VALIDATE_PRODUCT = YES; 193 | }; 194 | name = Release; 195 | }; 196 | 58B511F01A9E6C8500147676 /* Debug */ = { 197 | isa = XCBuildConfiguration; 198 | buildSettings = { 199 | LIBRARY_SEARCH_PATHS = "$(inherited)"; 200 | OTHER_LDFLAGS = "-ObjC"; 201 | PRODUCT_NAME = RNNowPlaying; 202 | SKIP_INSTALL = YES; 203 | }; 204 | name = Debug; 205 | }; 206 | 58B511F11A9E6C8500147676 /* Release */ = { 207 | isa = XCBuildConfiguration; 208 | buildSettings = { 209 | LIBRARY_SEARCH_PATHS = "$(inherited)"; 210 | OTHER_LDFLAGS = "-ObjC"; 211 | PRODUCT_NAME = RNNowPlaying; 212 | SKIP_INSTALL = YES; 213 | }; 214 | name = Release; 215 | }; 216 | /* End XCBuildConfiguration section */ 217 | 218 | /* Begin XCConfigurationList section */ 219 | 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "RNNowPlaying" */ = { 220 | isa = XCConfigurationList; 221 | buildConfigurations = ( 222 | 58B511ED1A9E6C8500147676 /* Debug */, 223 | 58B511EE1A9E6C8500147676 /* Release */, 224 | ); 225 | defaultConfigurationIsVisible = 0; 226 | defaultConfigurationName = Release; 227 | }; 228 | 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RNNowPlaying" */ = { 229 | isa = XCConfigurationList; 230 | buildConfigurations = ( 231 | 58B511F01A9E6C8500147676 /* Debug */, 232 | 58B511F11A9E6C8500147676 /* Release */, 233 | ); 234 | defaultConfigurationIsVisible = 0; 235 | defaultConfigurationName = Release; 236 | }; 237 | /* End XCConfigurationList section */ 238 | }; 239 | rootObject = 58B511D31A9E6C8500147676 /* Project object */; 240 | } 241 | --------------------------------------------------------------------------------