react-native-audio-api 0.8.0-nightly-daaceff-20250902 → 0.8.1-nightly-2c9c6a6-20250903

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/README.md +6 -7
  2. package/android/build.gradle +1 -1
  3. package/android/src/main/cpp/audioapi/CMakeLists.txt +3 -12
  4. package/android/src/main/cpp/audioapi/android/core/AudioDecoder.cpp +51 -21
  5. package/android/src/main/jniLibs/arm64-v8a/libavcodec.so +0 -0
  6. package/android/src/main/jniLibs/arm64-v8a/libavformat.so +0 -0
  7. package/android/src/main/jniLibs/arm64-v8a/libavutil.so +0 -0
  8. package/android/src/main/jniLibs/arm64-v8a/libswresample.so +0 -0
  9. package/android/src/main/jniLibs/armeabi-v7a/libavcodec.so +0 -0
  10. package/android/src/main/jniLibs/armeabi-v7a/libavformat.so +0 -0
  11. package/android/src/main/jniLibs/armeabi-v7a/libavutil.so +0 -0
  12. package/android/src/main/jniLibs/armeabi-v7a/libswresample.so +0 -0
  13. package/android/src/main/jniLibs/x86/libavcodec.so +0 -0
  14. package/android/src/main/jniLibs/x86/libavformat.so +0 -0
  15. package/android/src/main/jniLibs/x86/libavutil.so +0 -0
  16. package/android/src/main/jniLibs/x86/libswresample.so +0 -0
  17. package/android/src/main/jniLibs/x86_64/libavcodec.so +0 -0
  18. package/android/src/main/jniLibs/x86_64/libavformat.so +0 -0
  19. package/android/src/main/jniLibs/x86_64/libavutil.so +0 -0
  20. package/android/src/main/jniLibs/x86_64/libswresample.so +0 -0
  21. package/common/cpp/audioapi/core/utils/AudioDecoder.h +61 -0
  22. package/common/cpp/audioapi/core/utils/AudioNodeManager.cpp +14 -2
  23. package/common/cpp/audioapi/core/utils/AudioNodeManager.h +3 -0
  24. package/common/cpp/audioapi/external/libavcodec.xcframework/Info.plist +5 -5
  25. package/common/cpp/audioapi/external/libavcodec.xcframework/ios-arm64/libavcodec.framework/libavcodec +0 -0
  26. package/common/cpp/audioapi/external/libavcodec.xcframework/ios-arm64_x86_64-simulator/libavcodec.framework/libavcodec +0 -0
  27. package/common/cpp/audioapi/external/libavformat.xcframework/Info.plist +5 -5
  28. package/common/cpp/audioapi/external/libavformat.xcframework/ios-arm64/libavformat.framework/libavformat +0 -0
  29. package/common/cpp/audioapi/external/libavformat.xcframework/ios-arm64_x86_64-simulator/libavformat.framework/libavformat +0 -0
  30. package/common/cpp/audioapi/external/libavutil.xcframework/Info.plist +5 -5
  31. package/common/cpp/audioapi/external/libavutil.xcframework/ios-arm64/libavutil.framework/libavutil +0 -0
  32. package/common/cpp/audioapi/external/libavutil.xcframework/ios-arm64_x86_64-simulator/libavutil.framework/libavutil +0 -0
  33. package/common/cpp/audioapi/external/libswresample.xcframework/ios-arm64/libswresample.framework/libswresample +0 -0
  34. package/common/cpp/audioapi/external/libswresample.xcframework/ios-arm64_x86_64-simulator/libswresample.framework/libswresample +0 -0
  35. package/common/cpp/audioapi/libs/ffmpeg/FFmpegDecoding.cpp +402 -0
  36. package/common/cpp/audioapi/libs/ffmpeg/FFmpegDecoding.h +27 -0
  37. package/common/cpp/audioapi/libs/ffmpeg/ffmpeg_setup.sh +11 -10
  38. package/common/cpp/audioapi/utils/SpscChannel.hpp +47 -38
  39. package/common/cpp/test/CMakeLists.txt +2 -0
  40. package/ios/audioapi/ios/core/AudioDecoder.mm +22 -2
  41. package/lib/commonjs/core/AudioContext.js.map +1 -1
  42. package/lib/module/core/AudioContext.js.map +1 -1
  43. package/lib/typescript/core/AudioContext.d.ts +1 -1
  44. package/lib/typescript/core/AudioContext.d.ts.map +1 -1
  45. package/lib/typescript/interfaces.d.ts +1 -1
  46. package/lib/typescript/interfaces.d.ts.map +1 -1
  47. package/package.json +1 -1
  48. package/src/core/AudioContext.ts +1 -1
  49. package/src/interfaces.ts +1 -1
  50. package/common/cpp/audioapi/external/libavcodec.xcframework/ios-arm64_x86_64-simulator/libavcodec.framework/libavcodec.framework/Info.plist +0 -1
  51. package/common/cpp/audioapi/external/libavcodec.xcframework/ios-arm64_x86_64-simulator/libavcodec.framework/libavcodec.framework/libavcodec +0 -0
package/README.md CHANGED
@@ -53,16 +53,15 @@ check out the [Getting Started](https://docs.swmansion.com/react-native-audio-ap
53
53
  **Microphone support** 🎙️ <br />
54
54
  Grab audio data from device microphone or connected device, connect it to the audio graph or stream through the internet <br />
55
55
 
56
- **Custom Audio Processor** 🎙️ <br />
56
+ **Custom Audio Processor** ⚙️ <br />
57
57
  Write your own processing AudioNode <br />
58
58
 
59
- - **Decoding support for m4a/mp4/aac/ogg/opus** 🐎 <br />
60
- Ability to decode m4a/mp4/aac/ogg/opus audio files, allowing for playback of these formats in the audio graph.
61
- <br />
59
+ - <sub>[![Released in 0.8.0](https://img.shields.io/badge/Released_in-0.8.0-green)](https://github.com/software-mansion/react-native-audio-api/releases/tag/0.8.0)</sub> <br />
60
+ **Decoding support for m4a/mp4/aac/ogg/opus** 📁 <br />
61
+ Ability to decode m4a/mp4/aac/ogg/opus audio files, allowing for playback of these formats in the audio graph. <br />
62
62
 
63
- - **HLS streaming support** 🐎 <br />
64
- Ability to stream audio from HLS sources, allowing for playback of live audio streams or pre-recorded audio files.
65
- <br />
63
+ **HLS streaming support** 🌊 <br />
64
+ Ability to stream audio from HLS sources, allowing for playback of live audio streams or pre-recorded audio files. <br />
66
65
 
67
66
  - **JS Audio Worklets** 🐎 <br />
68
67
  Ability to run JS functions connected to the audio graph running on audio thread allowing for full customization of what happens to the audio signal.
@@ -230,7 +230,7 @@ dependencies {
230
230
 
231
231
  def assertMinimalReactNativeVersion = task assertMinimalReactNativeVersionTask {
232
232
  // If you change the minimal React Native version remember to update Compatibility Table in docs
233
- def minimalReactNativeVersion = 75
233
+ def minimalReactNativeVersion = 76
234
234
  onlyIf { REACT_NATIVE_MINOR_VERSION < minimalReactNativeVersion }
235
235
  doFirst {
236
236
  throw new GradleException("[AudioAPI] Unsupported React Native version. Please use $minimalReactNativeVersion. or newer.")
@@ -48,18 +48,9 @@ set(LINK_LIBRARIES
48
48
  oboe::oboe
49
49
  )
50
50
 
51
- if(ReactAndroid_VERSION_MINOR GREATER_EQUAL 76)
52
- set(RN_VERSION_LINK_LIBRARIES
53
- ReactAndroid::reactnative
54
- )
55
- else()
56
- set(RN_VERSION_LINK_LIBRARIES
57
- ReactAndroid::folly_runtime
58
- ReactAndroid::react_nativemodule_core
59
- ReactAndroid::glog
60
- ReactAndroid::reactnativejni
61
- )
62
- endif()
51
+ set(RN_VERSION_LINK_LIBRARIES
52
+ ReactAndroid::reactnative
53
+ )
63
54
 
64
55
  target_link_libraries(react-native-audio-api
65
56
  ${LINK_LIBRARIES}
@@ -7,11 +7,13 @@
7
7
  #define MINIAUDIO_IMPLEMENTATION
8
8
  #include <audioapi/libs/miniaudio/miniaudio.h>
9
9
 
10
+ #ifndef AUDIO_API_TEST_SUITE
11
+ #include <android/log.h>
12
+ #include <audioapi/libs/ffmpeg/FFmpegDecoding.h>
13
+ #endif
10
14
  #include <audioapi/libs/miniaudio/decoders/libopus/miniaudio_libopus.h>
11
15
  #include <audioapi/libs/miniaudio/decoders/libvorbis/miniaudio_libvorbis.h>
12
16
 
13
- // #include <android/log.h>
14
-
15
17
  namespace audioapi {
16
18
 
17
19
  // Decoding audio in fixed-size chunks because total frame count can't be
@@ -62,77 +64,105 @@ std::shared_ptr<AudioBus> AudioDecoder::makeAudioBusFromInt16Buffer(
62
64
 
63
65
  std::shared_ptr<AudioBus> AudioDecoder::decodeWithFilePath(
64
66
  const std::string &path) const {
67
+ #ifndef AUDIO_API_TEST_SUITE
68
+ std::vector<int16_t> buffer;
69
+ if (AudioDecoder::pathHasExtension(path, {".mp4", ".m4a", ".aac"})) {
70
+ buffer =
71
+ ffmpegdecoding::decodeWithFilePath(path, static_cast<int>(sampleRate_));
72
+ if (buffer.empty()) {
73
+ __android_log_print(
74
+ ANDROID_LOG_ERROR,
75
+ "AudioDecoder",
76
+ "Failed to decode with FFmpeg: %s",
77
+ path.c_str());
78
+ return nullptr;
79
+ }
80
+ return makeAudioBusFromInt16Buffer(buffer, numChannels_, sampleRate_);
81
+ }
65
82
  ma_decoder decoder;
66
83
  ma_decoder_config config = ma_decoder_config_init(
67
84
  ma_format_s16, numChannels_, static_cast<int>(sampleRate_));
68
- #ifndef AUDIO_API_TEST_SUITE
69
85
  ma_decoding_backend_vtable *customBackends[] = {
70
86
  ma_decoding_backend_libvorbis, ma_decoding_backend_libopus};
71
87
 
72
88
  config.ppCustomBackendVTables = customBackends;
73
89
  config.customBackendCount =
74
90
  sizeof(customBackends) / sizeof(customBackends[0]);
75
- #endif
76
91
 
77
92
  if (ma_decoder_init_file(path.c_str(), &config, &decoder) != MA_SUCCESS) {
78
- // __android_log_print(
79
- // ANDROID_LOG_ERROR,
80
- // "AudioDecoder",
81
- // "Failed to initialize decoder for file: %s",
82
- // path.c_str());
93
+ __android_log_print(
94
+ ANDROID_LOG_ERROR,
95
+ "AudioDecoder",
96
+ "Failed to initialize decoder for file: %s",
97
+ path.c_str());
83
98
  ma_decoder_uninit(&decoder);
84
99
  return nullptr;
85
100
  }
86
101
 
87
102
  ma_uint64 framesRead = 0;
88
- auto buffer = readAllPcmFrames(decoder, numChannels_, framesRead);
103
+ buffer = readAllPcmFrames(decoder, numChannels_, framesRead);
89
104
  if (framesRead == 0) {
90
- // __android_log_print(ANDROID_LOG_ERROR, "AudioDecoder", "Failed to
91
- // decode");
105
+ __android_log_print(ANDROID_LOG_ERROR, "AudioDecoder", "Failed to decode");
92
106
  ma_decoder_uninit(&decoder);
93
107
  return nullptr;
94
108
  }
95
109
 
96
110
  ma_decoder_uninit(&decoder);
97
111
  return makeAudioBusFromInt16Buffer(buffer, numChannels_, sampleRate_);
112
+ #else
113
+ return nullptr;
114
+ #endif
98
115
  }
99
116
 
100
117
  std::shared_ptr<AudioBus> AudioDecoder::decodeWithMemoryBlock(
101
118
  const void *data,
102
119
  size_t size) const {
120
+ #ifndef AUDIO_API_TEST_SUITE
121
+ std::vector<int16_t> buffer;
122
+ const AudioFormat format = AudioDecoder::detectAudioFormat(data, size);
123
+ if (format == AudioFormat::MP4 || format == AudioFormat::M4A ||
124
+ format == AudioFormat::AAC) {
125
+ buffer = ffmpegdecoding::decodeWithMemoryBlock(data, size, sampleRate_);
126
+ if (buffer.empty()) {
127
+ __android_log_print(
128
+ ANDROID_LOG_ERROR, "AudioDecoder", "Failed to decode with FFmpeg");
129
+ return nullptr;
130
+ }
131
+ return makeAudioBusFromInt16Buffer(buffer, numChannels_, sampleRate_);
132
+ }
103
133
  ma_decoder decoder;
104
134
  ma_decoder_config config = ma_decoder_config_init(
105
135
  ma_format_s16, numChannels_, static_cast<int>(sampleRate_));
106
136
 
107
- #ifndef AUDIO_API_TEST_SUITE
108
137
  ma_decoding_backend_vtable *customBackends[] = {
109
138
  ma_decoding_backend_libvorbis, ma_decoding_backend_libopus};
110
139
 
111
140
  config.ppCustomBackendVTables = customBackends;
112
141
  config.customBackendCount =
113
142
  sizeof(customBackends) / sizeof(customBackends[0]);
114
- #endif
115
143
 
116
144
  if (ma_decoder_init_memory(data, size, &config, &decoder) != MA_SUCCESS) {
117
- // __android_log_print(
118
- // ANDROID_LOG_ERROR,
119
- // "AudioDecoder",
120
- // "Failed to initialize decoder for memory block");
145
+ __android_log_print(
146
+ ANDROID_LOG_ERROR,
147
+ "AudioDecoder",
148
+ "Failed to initialize decoder for memory block");
121
149
  ma_decoder_uninit(&decoder);
122
150
  return nullptr;
123
151
  }
124
152
 
125
153
  ma_uint64 framesRead = 0;
126
- auto buffer = readAllPcmFrames(decoder, numChannels_, framesRead);
154
+ buffer = readAllPcmFrames(decoder, numChannels_, framesRead);
127
155
  if (framesRead == 0) {
128
- // __android_log_print(ANDROID_LOG_ERROR, "AudioDecoder", "Failed to
129
- // decode");
156
+ __android_log_print(ANDROID_LOG_ERROR, "AudioDecoder", "Failed to decode");
130
157
  ma_decoder_uninit(&decoder);
131
158
  return nullptr;
132
159
  }
133
160
 
134
161
  ma_decoder_uninit(&decoder);
135
162
  return makeAudioBusFromInt16Buffer(buffer, numChannels_, sampleRate_);
163
+ #else
164
+ return nullptr;
165
+ #endif
136
166
  }
137
167
 
138
168
  std::shared_ptr<AudioBus> AudioDecoder::decodeWithPCMInBase64(
@@ -5,9 +5,23 @@
5
5
  #include <memory>
6
6
  #include <string>
7
7
  #include <vector>
8
+ #include <cstring>
9
+ #include <algorithm>
8
10
 
9
11
  namespace audioapi {
10
12
 
13
+ enum class AudioFormat {
14
+ UNKNOWN,
15
+ WAV,
16
+ OGG,
17
+ FLAC,
18
+ AAC,
19
+ MP3,
20
+ M4A,
21
+ MP4,
22
+ MOV
23
+ };
24
+
11
25
  class AudioBus;
12
26
 
13
27
  static constexpr int CHUNK_SIZE = 4096;
@@ -73,6 +87,53 @@ class AudioDecoder {
73
87
  stretch_deinit(stretcher);
74
88
  }
75
89
 
90
+ static AudioFormat detectAudioFormat(const void* data, size_t size) {
91
+ if (size < 12) return AudioFormat::UNKNOWN;
92
+ const auto* bytes = static_cast<const unsigned char*>(data);
93
+
94
+ // WAV/RIFF
95
+ if (std::memcmp(bytes, "RIFF", 4) == 0 && std::memcmp(bytes + 8, "WAVE", 4) == 0)
96
+ return AudioFormat::WAV;
97
+
98
+ // OGG
99
+ if (std::memcmp(bytes, "OggS", 4) == 0)
100
+ return AudioFormat::OGG;
101
+
102
+ // FLAC
103
+ if (std::memcmp(bytes, "fLaC", 4) == 0)
104
+ return AudioFormat::FLAC;
105
+
106
+ // AAC starts with 0xFF 0xF1 or 0xFF 0xF9
107
+ if (bytes[0] == 0xFF && (bytes[1] & 0xF6) == 0xF0)
108
+ return AudioFormat::AAC;
109
+
110
+ // MP3: "ID3" or 11-bit frame sync (0xFF 0xE0)
111
+ if (std::memcmp(bytes, "ID3", 3) == 0)
112
+ return AudioFormat::MP3;
113
+ if (bytes[0] == 0xFF && (bytes[1] & 0xE0) == 0xE0)
114
+ return AudioFormat::MP3;
115
+
116
+ if (std::memcmp(bytes + 4, "ftyp", 4) == 0) {
117
+ if (std::memcmp(bytes + 8, "M4A ", 4) == 0)
118
+ return AudioFormat::M4A;
119
+ else if (std::memcmp(bytes + 8, "qt ", 4) == 0)
120
+ return AudioFormat::MOV;
121
+ return AudioFormat::MP4;
122
+ }
123
+ return AudioFormat::UNKNOWN;
124
+ }
125
+
126
+ static inline bool pathHasExtension(const std::string &path, const std::vector<std::string> &extensions) {
127
+ std::string pathLower = path;
128
+ std::transform(pathLower.begin(), pathLower.end(), pathLower.begin(), ::tolower);
129
+ for (const auto& ext : extensions) {
130
+ if (pathLower.ends_with(ext))
131
+ return true;
132
+ }
133
+ return false;
134
+ }
135
+
136
+
76
137
  [[nodiscard]] static inline int16_t floatToInt16(float sample) {
77
138
  return static_cast<int16_t>(sample * 32768.0f);
78
139
  }
@@ -213,6 +213,18 @@ void AudioNodeManager::handleAddToDeconstructionEvent(
213
213
  }
214
214
  }
215
215
 
216
+ template <typename U>
217
+ inline bool AudioNodeManager::nodeCanBeDestructed(
218
+ std::shared_ptr<U> const &node) {
219
+ // If the node is an AudioScheduledSourceNode, we need to check if it is
220
+ // playing
221
+ if constexpr (std::is_base_of_v<AudioScheduledSourceNode, U>) {
222
+ return node.use_count() == 1 &&
223
+ (node->isUnscheduled() || node->isFinished());
224
+ }
225
+ return node.use_count() == 1;
226
+ }
227
+
216
228
  template <typename U>
217
229
  void AudioNodeManager::prepareNodesForDestruction(
218
230
  std::vector<std::shared_ptr<U>> &vec) {
@@ -238,10 +250,10 @@ void AudioNodeManager::prepareNodesForDestruction(
238
250
  // nodes in range [begin, vec.size()) should be deleted
239
251
  // so new size of the vector will be `begin`
240
252
  while (begin <= end) {
241
- while (begin < end && vec[end].use_count() == 1) {
253
+ while (begin < end && AudioNodeManager::nodeCanBeDestructed(vec[end])) {
242
254
  end--;
243
255
  }
244
- if (vec[begin].use_count() == 1) {
256
+ if (AudioNodeManager::nodeCanBeDestructed(vec[begin])) {
245
257
  std::swap(vec[begin], vec[end]);
246
258
  end--;
247
259
  }
@@ -126,6 +126,9 @@ class AudioNodeManager {
126
126
 
127
127
  template <typename U>
128
128
  void prepareNodesForDestruction(std::vector<std::shared_ptr<U>> &vec);
129
+
130
+ template <typename U>
131
+ inline static bool nodeCanBeDestructed(std::shared_ptr<U> const& node);
129
132
  };
130
133
 
131
134
  #undef AUDIO_NODE_MANAGER_SPSC_OPTIONS
@@ -8,32 +8,32 @@
8
8
  <key>BinaryPath</key>
9
9
  <string>libavcodec.framework/libavcodec</string>
10
10
  <key>LibraryIdentifier</key>
11
- <string>ios-arm64</string>
11
+ <string>ios-arm64_x86_64-simulator</string>
12
12
  <key>LibraryPath</key>
13
13
  <string>libavcodec.framework</string>
14
14
  <key>SupportedArchitectures</key>
15
15
  <array>
16
16
  <string>arm64</string>
17
+ <string>x86_64</string>
17
18
  </array>
18
19
  <key>SupportedPlatform</key>
19
20
  <string>ios</string>
21
+ <key>SupportedPlatformVariant</key>
22
+ <string>simulator</string>
20
23
  </dict>
21
24
  <dict>
22
25
  <key>BinaryPath</key>
23
26
  <string>libavcodec.framework/libavcodec</string>
24
27
  <key>LibraryIdentifier</key>
25
- <string>ios-arm64_x86_64-simulator</string>
28
+ <string>ios-arm64</string>
26
29
  <key>LibraryPath</key>
27
30
  <string>libavcodec.framework</string>
28
31
  <key>SupportedArchitectures</key>
29
32
  <array>
30
33
  <string>arm64</string>
31
- <string>x86_64</string>
32
34
  </array>
33
35
  <key>SupportedPlatform</key>
34
36
  <string>ios</string>
35
- <key>SupportedPlatformVariant</key>
36
- <string>simulator</string>
37
37
  </dict>
38
38
  </array>
39
39
  <key>CFBundlePackageType</key>
@@ -8,32 +8,32 @@
8
8
  <key>BinaryPath</key>
9
9
  <string>libavformat.framework/libavformat</string>
10
10
  <key>LibraryIdentifier</key>
11
- <string>ios-arm64</string>
11
+ <string>ios-arm64_x86_64-simulator</string>
12
12
  <key>LibraryPath</key>
13
13
  <string>libavformat.framework</string>
14
14
  <key>SupportedArchitectures</key>
15
15
  <array>
16
16
  <string>arm64</string>
17
+ <string>x86_64</string>
17
18
  </array>
18
19
  <key>SupportedPlatform</key>
19
20
  <string>ios</string>
21
+ <key>SupportedPlatformVariant</key>
22
+ <string>simulator</string>
20
23
  </dict>
21
24
  <dict>
22
25
  <key>BinaryPath</key>
23
26
  <string>libavformat.framework/libavformat</string>
24
27
  <key>LibraryIdentifier</key>
25
- <string>ios-arm64_x86_64-simulator</string>
28
+ <string>ios-arm64</string>
26
29
  <key>LibraryPath</key>
27
30
  <string>libavformat.framework</string>
28
31
  <key>SupportedArchitectures</key>
29
32
  <array>
30
33
  <string>arm64</string>
31
- <string>x86_64</string>
32
34
  </array>
33
35
  <key>SupportedPlatform</key>
34
36
  <string>ios</string>
35
- <key>SupportedPlatformVariant</key>
36
- <string>simulator</string>
37
37
  </dict>
38
38
  </array>
39
39
  <key>CFBundlePackageType</key>
@@ -8,32 +8,32 @@
8
8
  <key>BinaryPath</key>
9
9
  <string>libavutil.framework/libavutil</string>
10
10
  <key>LibraryIdentifier</key>
11
- <string>ios-arm64_x86_64-simulator</string>
11
+ <string>ios-arm64</string>
12
12
  <key>LibraryPath</key>
13
13
  <string>libavutil.framework</string>
14
14
  <key>SupportedArchitectures</key>
15
15
  <array>
16
16
  <string>arm64</string>
17
- <string>x86_64</string>
18
17
  </array>
19
18
  <key>SupportedPlatform</key>
20
19
  <string>ios</string>
21
- <key>SupportedPlatformVariant</key>
22
- <string>simulator</string>
23
20
  </dict>
24
21
  <dict>
25
22
  <key>BinaryPath</key>
26
23
  <string>libavutil.framework/libavutil</string>
27
24
  <key>LibraryIdentifier</key>
28
- <string>ios-arm64</string>
25
+ <string>ios-arm64_x86_64-simulator</string>
29
26
  <key>LibraryPath</key>
30
27
  <string>libavutil.framework</string>
31
28
  <key>SupportedArchitectures</key>
32
29
  <array>
33
30
  <string>arm64</string>
31
+ <string>x86_64</string>
34
32
  </array>
35
33
  <key>SupportedPlatform</key>
36
34
  <string>ios</string>
35
+ <key>SupportedPlatformVariant</key>
36
+ <string>simulator</string>
37
37
  </dict>
38
38
  </array>
39
39
  <key>CFBundlePackageType</key>