@ion299/sdk-react-native 0.1.0-beta.5 → 0.1.0-beta.6

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 (40) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/android/src/main/java/com/chatplatform/sdk/ChatSdkAudioPlayerModule.kt +201 -0
  3. package/android/src/main/java/com/chatplatform/sdk/ChatSdkPackage.kt +1 -0
  4. package/ios/ChatSdkAudioPlayer.m +25 -0
  5. package/ios/ChatSdkAudioPlayer.swift +193 -0
  6. package/lib/commonjs/audio/audioController.js +145 -0
  7. package/lib/commonjs/audio/audioController.js.map +1 -0
  8. package/lib/commonjs/components/AudioMessage.js +198 -0
  9. package/lib/commonjs/components/AudioMessage.js.map +1 -0
  10. package/lib/commonjs/components/MessageBubble.js +9 -0
  11. package/lib/commonjs/components/MessageBubble.js.map +1 -1
  12. package/lib/commonjs/native/NativeChatSdkAudioPlayer.js +28 -0
  13. package/lib/commonjs/native/NativeChatSdkAudioPlayer.js.map +1 -0
  14. package/lib/module/audio/audioController.js +140 -0
  15. package/lib/module/audio/audioController.js.map +1 -0
  16. package/lib/module/components/AudioMessage.js +193 -0
  17. package/lib/module/components/AudioMessage.js.map +1 -0
  18. package/lib/module/components/MessageBubble.js +9 -0
  19. package/lib/module/components/MessageBubble.js.map +1 -1
  20. package/lib/module/native/NativeChatSdkAudioPlayer.js +23 -0
  21. package/lib/module/native/NativeChatSdkAudioPlayer.js.map +1 -0
  22. package/lib/typescript/commonjs/audio/audioController.d.ts +25 -0
  23. package/lib/typescript/commonjs/audio/audioController.d.ts.map +1 -0
  24. package/lib/typescript/commonjs/components/AudioMessage.d.ts +11 -0
  25. package/lib/typescript/commonjs/components/AudioMessage.d.ts.map +1 -0
  26. package/lib/typescript/commonjs/components/MessageBubble.d.ts.map +1 -1
  27. package/lib/typescript/commonjs/native/NativeChatSdkAudioPlayer.d.ts +20 -0
  28. package/lib/typescript/commonjs/native/NativeChatSdkAudioPlayer.d.ts.map +1 -0
  29. package/lib/typescript/module/audio/audioController.d.ts +25 -0
  30. package/lib/typescript/module/audio/audioController.d.ts.map +1 -0
  31. package/lib/typescript/module/components/AudioMessage.d.ts +11 -0
  32. package/lib/typescript/module/components/AudioMessage.d.ts.map +1 -0
  33. package/lib/typescript/module/components/MessageBubble.d.ts.map +1 -1
  34. package/lib/typescript/module/native/NativeChatSdkAudioPlayer.d.ts +20 -0
  35. package/lib/typescript/module/native/NativeChatSdkAudioPlayer.d.ts.map +1 -0
  36. package/package.json +1 -1
  37. package/src/audio/audioController.ts +144 -0
  38. package/src/components/AudioMessage.tsx +198 -0
  39. package/src/components/MessageBubble.tsx +6 -0
  40. package/src/native/NativeChatSdkAudioPlayer.ts +54 -0
package/CHANGELOG.md CHANGED
@@ -6,6 +6,21 @@
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.1.0-beta.6]
10
+
11
+ ### Added
12
+ - Аудио-вложения (голосовые сообщения) теперь проигрываются прямо в чате:
13
+ встроенный плеер с кнопкой play/pause, прогресс-баром и перемоткой по тапу.
14
+ Раньше аудио показывалось как обычный файл (`.webm`) только для скачивания.
15
+ - Собственный нативный модуль воспроизведения `ChatSdkAudioPlayer`
16
+ (Android `MediaPlayer`, iOS `AVPlayer`) — без сторонних JS-зависимостей.
17
+ Если модуль не собран, аудио откатывается на обычный файловый блок.
18
+
19
+ ### Notes
20
+ - iOS (`AVPlayer`) не декодирует контейнер WebM/Opus: голосовые, записанные
21
+ в браузере как `audio/webm`, на iOS воспроизвести нельзя — их нужно
22
+ транскодировать на бэкенде (например, в `m4a`/`aac`). Android их играет.
23
+
9
24
  ## [0.1.0-beta.5]
10
25
 
11
26
  ### Changed
@@ -0,0 +1,201 @@
1
+ package com.chatplatform.sdk
2
+
3
+ import android.media.AudioAttributes
4
+ import android.media.MediaPlayer
5
+ import android.net.Uri
6
+ import android.os.Handler
7
+ import android.os.HandlerThread
8
+ import com.facebook.react.bridge.Arguments
9
+ import com.facebook.react.bridge.Promise
10
+ import com.facebook.react.bridge.ReactApplicationContext
11
+ import com.facebook.react.bridge.ReactContextBaseJavaModule
12
+ import com.facebook.react.bridge.ReactMethod
13
+ import com.facebook.react.bridge.ReadableMap
14
+ import com.facebook.react.bridge.WritableMap
15
+ import com.facebook.react.modules.core.DeviceEventManagerModule
16
+
17
+ /**
18
+ * Проигрывает аудио-вложения (голосовые сообщения) прямо в чате.
19
+ * В каждый момент времени активен ровно один плеер — запуск нового
20
+ * освобождает предыдущий. Состояние отдаётся в JS событием
21
+ * "ChatSdkAudioState" { key, positionMillis, durationMillis, state }.
22
+ */
23
+ class ChatSdkAudioPlayerModule(reactContext: ReactApplicationContext) :
24
+ ReactContextBaseJavaModule(reactContext) {
25
+
26
+ override fun getName(): String = NAME
27
+
28
+ private val thread = HandlerThread("ChatSdkAudioPlayer").apply { start() }
29
+ private val handler = Handler(thread.looper)
30
+
31
+ private var player: MediaPlayer? = null
32
+ private var currentKey: String? = null
33
+ private var prepared = false
34
+
35
+ private val tick = object : Runnable {
36
+ override fun run() {
37
+ val p = player ?: return
38
+ try {
39
+ if (p.isPlaying) {
40
+ emit(currentKey, p.currentPosition.toLong(), safeDuration(p), "playing")
41
+ handler.postDelayed(this, 250)
42
+ }
43
+ } catch (_: Throwable) {
44
+ // плеер уже освобождён — просто перестаём тикать
45
+ }
46
+ }
47
+ }
48
+
49
+ @ReactMethod
50
+ fun addListener(eventName: String) {
51
+
52
+ }
53
+
54
+ @ReactMethod
55
+ fun removeListeners(count: Int) {
56
+
57
+ }
58
+
59
+ @ReactMethod
60
+ fun play(key: String, url: String, headers: ReadableMap?, promise: Promise) {
61
+ val hdr = HashMap<String, String>()
62
+ headers?.toHashMap()?.forEach { (k, v) -> if (v is String) hdr[k] = v }
63
+
64
+ handler.post {
65
+ try {
66
+ val existing = player
67
+ if (currentKey == key && existing != null && prepared) {
68
+ existing.start()
69
+ emit(key, existing.currentPosition.toLong(), safeDuration(existing), "playing")
70
+ handler.removeCallbacks(tick)
71
+ handler.post(tick)
72
+ promise.resolve(null)
73
+ return@post
74
+ }
75
+
76
+ releaseInternal()
77
+ currentKey = key
78
+ prepared = false
79
+ emit(key, 0, 0, "loading")
80
+
81
+ val mp = MediaPlayer()
82
+ mp.setAudioAttributes(
83
+ AudioAttributes.Builder()
84
+ .setUsage(AudioAttributes.USAGE_MEDIA)
85
+ .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
86
+ .build(),
87
+ )
88
+ if (hdr.isEmpty()) {
89
+ mp.setDataSource(reactApplicationContext, Uri.parse(url))
90
+ } else {
91
+ mp.setDataSource(reactApplicationContext, Uri.parse(url), hdr)
92
+ }
93
+ mp.setOnPreparedListener { p ->
94
+ if (currentKey != key) return@setOnPreparedListener
95
+ prepared = true
96
+ p.start()
97
+ emit(key, 0, safeDuration(p), "playing")
98
+ handler.removeCallbacks(tick)
99
+ handler.post(tick)
100
+ }
101
+ mp.setOnCompletionListener { p ->
102
+ handler.removeCallbacks(tick)
103
+ emit(key, safeDuration(p), safeDuration(p), "ended")
104
+ }
105
+ mp.setOnErrorListener { _, _, _ ->
106
+ handler.removeCallbacks(tick)
107
+ emit(key, 0, 0, "error")
108
+ releaseInternal()
109
+ true
110
+ }
111
+ player = mp
112
+ mp.prepareAsync()
113
+ promise.resolve(null)
114
+ } catch (e: Throwable) {
115
+ emit(key, 0, 0, "error")
116
+ releaseInternal()
117
+ promise.reject("AUDIO_PLAY_FAILED", e.message ?: "Не удалось воспроизвести аудио", e)
118
+ }
119
+ }
120
+ }
121
+
122
+ @ReactMethod
123
+ fun pause(key: String, promise: Promise) {
124
+ handler.post {
125
+ try {
126
+ val p = player
127
+ if (currentKey == key && p != null && prepared && p.isPlaying) {
128
+ p.pause()
129
+ handler.removeCallbacks(tick)
130
+ emit(key, p.currentPosition.toLong(), safeDuration(p), "paused")
131
+ }
132
+ promise.resolve(null)
133
+ } catch (e: Throwable) {
134
+ promise.reject("AUDIO_PAUSE_FAILED", e.message ?: "Ошибка паузы", e)
135
+ }
136
+ }
137
+ }
138
+
139
+ @ReactMethod
140
+ fun seek(key: String, positionMillis: Double, promise: Promise) {
141
+ handler.post {
142
+ try {
143
+ val p = player
144
+ if (currentKey == key && p != null && prepared) {
145
+ p.seekTo(positionMillis.toInt())
146
+ val state = if (p.isPlaying) "playing" else "paused"
147
+ emit(key, positionMillis.toLong(), safeDuration(p), state)
148
+ }
149
+ promise.resolve(null)
150
+ } catch (e: Throwable) {
151
+ promise.reject("AUDIO_SEEK_FAILED", e.message ?: "Ошибка перемотки", e)
152
+ }
153
+ }
154
+ }
155
+
156
+ @ReactMethod
157
+ fun stop(key: String, promise: Promise) {
158
+ handler.post {
159
+ try {
160
+ if (currentKey == key) {
161
+ handler.removeCallbacks(tick)
162
+ releaseInternal()
163
+ emit(key, 0, 0, "stopped")
164
+ }
165
+ promise.resolve(null)
166
+ } catch (e: Throwable) {
167
+ promise.reject("AUDIO_STOP_FAILED", e.message ?: "Ошибка остановки", e)
168
+ }
169
+ }
170
+ }
171
+
172
+ private fun releaseInternal() {
173
+ try { player?.reset() } catch (_: Throwable) {}
174
+ try { player?.release() } catch (_: Throwable) {}
175
+ player = null
176
+ prepared = false
177
+ }
178
+
179
+ private fun safeDuration(p: MediaPlayer): Long =
180
+ try {
181
+ val d = p.duration
182
+ if (d > 0) d.toLong() else 0L
183
+ } catch (_: Throwable) {
184
+ 0L
185
+ }
186
+
187
+ private fun emit(key: String?, position: Long, duration: Long, state: String) {
188
+ val map: WritableMap = Arguments.createMap()
189
+ map.putString("key", key ?: "")
190
+ map.putDouble("positionMillis", position.toDouble())
191
+ map.putDouble("durationMillis", duration.toDouble())
192
+ map.putString("state", state)
193
+ reactApplicationContext
194
+ .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
195
+ .emit("ChatSdkAudioState", map)
196
+ }
197
+
198
+ companion object {
199
+ const val NAME = "ChatSdkAudioPlayer"
200
+ }
201
+ }
@@ -9,6 +9,7 @@ class ChatSdkPackage : ReactPackage {
9
9
  override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> = listOf(
10
10
  ChatSdkFilePickerModule(reactContext),
11
11
  ChatSdkDownloaderModule(reactContext),
12
+ ChatSdkAudioPlayerModule(reactContext),
12
13
  )
13
14
 
14
15
  override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> = emptyList()
@@ -0,0 +1,25 @@
1
+ #import <React/RCTBridgeModule.h>
2
+ #import <React/RCTEventEmitter.h>
3
+
4
+ @interface RCT_EXTERN_MODULE(ChatSdkAudioPlayer, RCTEventEmitter)
5
+
6
+ RCT_EXTERN_METHOD(play:(NSString *)key
7
+ url:(NSString *)url
8
+ headers:(NSDictionary *)headers
9
+ resolver:(RCTPromiseResolveBlock)resolve
10
+ rejecter:(RCTPromiseRejectBlock)reject)
11
+
12
+ RCT_EXTERN_METHOD(pause:(NSString *)key
13
+ resolver:(RCTPromiseResolveBlock)resolve
14
+ rejecter:(RCTPromiseRejectBlock)reject)
15
+
16
+ RCT_EXTERN_METHOD(seek:(NSString *)key
17
+ positionMillis:(nonnull NSNumber *)positionMillis
18
+ resolver:(RCTPromiseResolveBlock)resolve
19
+ rejecter:(RCTPromiseRejectBlock)reject)
20
+
21
+ RCT_EXTERN_METHOD(stop:(NSString *)key
22
+ resolver:(RCTPromiseResolveBlock)resolve
23
+ rejecter:(RCTPromiseRejectBlock)reject)
24
+
25
+ @end
@@ -0,0 +1,193 @@
1
+ import Foundation
2
+ import AVFoundation
3
+ import React
4
+ @objc(ChatSdkAudioPlayer)
5
+ class ChatSdkAudioPlayer: RCTEventEmitter {
6
+
7
+ private var player: AVPlayer?
8
+ private var observedItem: AVPlayerItem?
9
+ private var timeObserver: Any?
10
+ private var endObserver: NSObjectProtocol?
11
+ private var currentKey: String?
12
+ private var durationMillis: Double = 0
13
+ private var listenerCount = 0
14
+
15
+ @objc override static func requiresMainQueueSetup() -> Bool { return false }
16
+ override func supportedEvents() -> [String]! { return ["ChatSdkAudioState"] }
17
+ override func startObserving() { listenerCount += 1 }
18
+ override func stopObserving() { listenerCount = max(0, listenerCount - 1) }
19
+
20
+ @objc(play:url:headers:resolver:rejecter:)
21
+ func play(_ key: String,
22
+ url urlString: String,
23
+ headers: NSDictionary?,
24
+ resolver resolve: @escaping RCTPromiseResolveBlock,
25
+ rejecter reject: @escaping RCTPromiseRejectBlock) {
26
+ DispatchQueue.main.async {
27
+ if self.currentKey == key, let p = self.player {
28
+ self.activateSession()
29
+ p.play()
30
+ self.emit(key: key, position: self.currentPositionMillis(), duration: self.durationMillis, state: "playing")
31
+ resolve(nil)
32
+ return
33
+ }
34
+
35
+ guard let url = URL(string: urlString) else {
36
+ reject("INVALID_URL", "URL не задан", nil)
37
+ return
38
+ }
39
+
40
+ self.teardown()
41
+ self.currentKey = key
42
+ self.emit(key: key, position: 0, duration: 0, state: "loading")
43
+
44
+ var options: [String: Any] = [:]
45
+ if let h = headers as? [String: String], !h.isEmpty {
46
+ options["AVURLAssetHTTPHeaderFieldsKey"] = h
47
+ }
48
+ let asset = AVURLAsset(url: url, options: options)
49
+ let item = AVPlayerItem(asset: asset)
50
+ let p = AVPlayer(playerItem: item)
51
+ self.player = p
52
+ self.observedItem = item
53
+
54
+ self.activateSession()
55
+
56
+ item.addObserver(self, forKeyPath: "status", options: [.new], context: nil)
57
+
58
+ let interval = CMTime(seconds: 0.25, preferredTimescale: 600)
59
+ self.timeObserver = p.addPeriodicTimeObserver(forInterval: interval, queue: .main) { [weak self] time in
60
+ guard let self = self, self.currentKey == key else { return }
61
+ if p.timeControlStatus == .playing {
62
+ let pos = CMTimeGetSeconds(time) * 1000
63
+ self.emit(key: key, position: pos.isFinite ? pos : 0, duration: self.durationMillis, state: "playing")
64
+ }
65
+ }
66
+
67
+ self.endObserver = NotificationCenter.default.addObserver(
68
+ forName: .AVPlayerItemDidPlayToEndTime,
69
+ object: item,
70
+ queue: .main,
71
+ ) { [weak self] _ in
72
+ guard let self = self, self.currentKey == key else { return }
73
+ self.emit(key: key, position: self.durationMillis, duration: self.durationMillis, state: "ended")
74
+ p.seek(to: .zero)
75
+ }
76
+
77
+ p.play()
78
+ resolve(nil)
79
+ }
80
+ }
81
+
82
+ @objc(pause:resolver:rejecter:)
83
+ func pause(_ key: String,
84
+ resolver resolve: @escaping RCTPromiseResolveBlock,
85
+ rejecter reject: @escaping RCTPromiseRejectBlock) {
86
+ DispatchQueue.main.async {
87
+ if self.currentKey == key, let p = self.player {
88
+ p.pause()
89
+ self.emit(key: key, position: self.currentPositionMillis(), duration: self.durationMillis, state: "paused")
90
+ }
91
+ resolve(nil)
92
+ }
93
+ }
94
+
95
+ @objc(seek:positionMillis:resolver:rejecter:)
96
+ func seek(_ key: String,
97
+ positionMillis: NSNumber,
98
+ resolver resolve: @escaping RCTPromiseResolveBlock,
99
+ rejecter reject: @escaping RCTPromiseRejectBlock) {
100
+ DispatchQueue.main.async {
101
+ guard self.currentKey == key, let p = self.player else {
102
+ resolve(nil)
103
+ return
104
+ }
105
+ let seconds = positionMillis.doubleValue / 1000
106
+ let target = CMTime(seconds: seconds, preferredTimescale: 600)
107
+ p.seek(to: target) { [weak self] _ in
108
+ guard let self = self, self.currentKey == key else { return }
109
+ let state = p.timeControlStatus == .playing ? "playing" : "paused"
110
+ self.emit(key: key, position: positionMillis.doubleValue, duration: self.durationMillis, state: state)
111
+ }
112
+ resolve(nil)
113
+ }
114
+ }
115
+
116
+ @objc(stop:resolver:rejecter:)
117
+ func stop(_ key: String,
118
+ resolver resolve: @escaping RCTPromiseResolveBlock,
119
+ rejecter reject: @escaping RCTPromiseRejectBlock) {
120
+ DispatchQueue.main.async {
121
+ if self.currentKey == key {
122
+ self.teardown()
123
+ self.deactivateSession()
124
+ self.emit(key: key, position: 0, duration: 0, state: "stopped")
125
+ }
126
+ resolve(nil)
127
+ }
128
+ }
129
+
130
+ override func observeValue(forKeyPath keyPath: String?,
131
+ of object: Any?,
132
+ change: [NSKeyValueChangeKey: Any]?,
133
+ context: UnsafeMutableRawPointer?) {
134
+ guard keyPath == "status", let item = object as? AVPlayerItem, item === observedItem else { return }
135
+ switch item.status {
136
+ case .readyToPlay:
137
+ let dur = CMTimeGetSeconds(item.duration) * 1000
138
+ durationMillis = (dur.isFinite && dur > 0) ? dur : 0
139
+ emit(key: currentKey, position: currentPositionMillis(), duration: durationMillis, state: "playing")
140
+ case .failed:
141
+ emit(key: currentKey, position: 0, duration: 0, state: "error")
142
+ teardown()
143
+ default:
144
+ break
145
+ }
146
+ }
147
+
148
+ private func currentPositionMillis() -> Double {
149
+ guard let p = player else { return 0 }
150
+ let s = CMTimeGetSeconds(p.currentTime())
151
+ return s.isFinite ? s * 1000 : 0
152
+ }
153
+
154
+ private func teardown() {
155
+ if let obs = timeObserver { player?.removeTimeObserver(obs); timeObserver = nil }
156
+ if let end = endObserver { NotificationCenter.default.removeObserver(end); endObserver = nil }
157
+ if let item = observedItem { item.removeObserver(self, forKeyPath: "status"); observedItem = nil }
158
+ player?.pause()
159
+ player = nil
160
+ durationMillis = 0
161
+ }
162
+
163
+ private func activateSession() {
164
+ do {
165
+ try AVAudioSession.sharedInstance().setCategory(.playback, mode: .spokenAudio, options: [])
166
+ try AVAudioSession.sharedInstance().setActive(true)
167
+ } catch {
168
+
169
+ }
170
+ }
171
+
172
+ private func deactivateSession() {
173
+ try? AVAudioSession.sharedInstance().setActive(false, options: .notifyOthersOnDeactivation)
174
+ }
175
+
176
+ private func emit(key: String?, position: Double, duration: Double, state: String) {
177
+ guard listenerCount > 0 else { return }
178
+ sendEvent(withName: "ChatSdkAudioState", body: [
179
+ "key": key ?? "",
180
+ "positionMillis": NSNumber(value: position),
181
+ "durationMillis": NSNumber(value: duration),
182
+ "state": state,
183
+ ])
184
+ }
185
+
186
+ override func invalidate() {
187
+ DispatchQueue.main.async {
188
+ self.teardown()
189
+ self.deactivateSession()
190
+ }
191
+ super.invalidate()
192
+ }
193
+ }
@@ -0,0 +1,145 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.audioController = void 0;
7
+ var _NativeChatSdkAudioPlayer = _interopRequireWildcard(require("../native/NativeChatSdkAudioPlayer.js"));
8
+ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
9
+ const IDLE = {
10
+ state: 'idle',
11
+ positionMillis: 0,
12
+ durationMillis: 0
13
+ };
14
+ class AudioController {
15
+ listeners = new Map();
16
+ states = new Map();
17
+ activeKey = null;
18
+ subscribed = false;
19
+ get isAvailable() {
20
+ return !!_NativeChatSdkAudioPlayer.default;
21
+ }
22
+ ensureSubscribed() {
23
+ if (this.subscribed || !_NativeChatSdkAudioPlayer.default) return;
24
+ this.subscribed = true;
25
+ (0, _NativeChatSdkAudioPlayer.onAudioState)(event => this.handleEvent(event));
26
+ }
27
+ handleEvent(event) {
28
+ const prev = this.getState(event.key);
29
+ let next;
30
+ switch (event.state) {
31
+ case 'loading':
32
+ next = {
33
+ state: 'loading',
34
+ positionMillis: 0,
35
+ durationMillis: event.durationMillis
36
+ };
37
+ break;
38
+ case 'playing':
39
+ next = {
40
+ state: 'playing',
41
+ positionMillis: event.positionMillis,
42
+ durationMillis: event.durationMillis || prev.durationMillis
43
+ };
44
+ break;
45
+ case 'paused':
46
+ next = {
47
+ state: 'paused',
48
+ positionMillis: event.positionMillis,
49
+ durationMillis: event.durationMillis || prev.durationMillis
50
+ };
51
+ break;
52
+ case 'ended':
53
+ case 'stopped':
54
+ next = {
55
+ state: 'idle',
56
+ positionMillis: 0,
57
+ durationMillis: event.durationMillis || prev.durationMillis
58
+ };
59
+ if (this.activeKey === event.key) this.activeKey = null;
60
+ break;
61
+ case 'error':
62
+ default:
63
+ next = {
64
+ state: 'error',
65
+ positionMillis: 0,
66
+ durationMillis: 0
67
+ };
68
+ if (this.activeKey === event.key) this.activeKey = null;
69
+ break;
70
+ }
71
+ this.setState(event.key, next);
72
+ }
73
+ setState(key, state) {
74
+ this.states.set(key, state);
75
+ this.listeners.get(key)?.forEach(listener => listener(state));
76
+ }
77
+ getState(key) {
78
+ return this.states.get(key) ?? IDLE;
79
+ }
80
+ subscribe(key, listener) {
81
+ this.ensureSubscribed();
82
+ let set = this.listeners.get(key);
83
+ if (!set) {
84
+ set = new Set();
85
+ this.listeners.set(key, set);
86
+ }
87
+ set.add(listener);
88
+ listener(this.getState(key));
89
+ return () => {
90
+ set.delete(listener);
91
+ if (set.size === 0) this.listeners.delete(key);
92
+ };
93
+ }
94
+ async play(key, url, headers = {}) {
95
+ if (!_NativeChatSdkAudioPlayer.default) return;
96
+ this.ensureSubscribed();
97
+ if (this.activeKey && this.activeKey !== key) {
98
+ const prevKey = this.activeKey;
99
+ const prev = this.getState(prevKey);
100
+ this.setState(prevKey, {
101
+ state: 'idle',
102
+ positionMillis: 0,
103
+ durationMillis: prev.durationMillis
104
+ });
105
+ }
106
+ this.activeKey = key;
107
+ const current = this.getState(key);
108
+ if (current.state === 'idle' || current.state === 'error') {
109
+ this.setState(key, {
110
+ ...current,
111
+ state: 'loading',
112
+ positionMillis: 0
113
+ });
114
+ }
115
+ try {
116
+ await _NativeChatSdkAudioPlayer.default.play(key, url, headers);
117
+ } catch {
118
+ this.setState(key, {
119
+ state: 'error',
120
+ positionMillis: 0,
121
+ durationMillis: 0
122
+ });
123
+ }
124
+ }
125
+ async pause(key) {
126
+ if (!_NativeChatSdkAudioPlayer.default) return;
127
+ try {
128
+ await _NativeChatSdkAudioPlayer.default.pause(key);
129
+ } catch {}
130
+ }
131
+ async seek(key, positionMillis) {
132
+ if (!_NativeChatSdkAudioPlayer.default) return;
133
+ try {
134
+ await _NativeChatSdkAudioPlayer.default.seek(key, positionMillis);
135
+ } catch {}
136
+ }
137
+ async stop(key) {
138
+ if (!_NativeChatSdkAudioPlayer.default) return;
139
+ try {
140
+ await _NativeChatSdkAudioPlayer.default.stop(key);
141
+ } catch {}
142
+ }
143
+ }
144
+ const audioController = exports.audioController = new AudioController();
145
+ //# sourceMappingURL=audioController.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["_NativeChatSdkAudioPlayer","_interopRequireWildcard","require","e","t","WeakMap","r","n","__esModule","o","i","f","__proto__","default","has","get","set","hasOwnProperty","call","Object","defineProperty","getOwnPropertyDescriptor","IDLE","state","positionMillis","durationMillis","AudioController","listeners","Map","states","activeKey","subscribed","isAvailable","AudioModule","ensureSubscribed","onAudioState","event","handleEvent","prev","getState","key","next","setState","forEach","listener","subscribe","Set","add","delete","size","play","url","headers","prevKey","current","pause","seek","stop","audioController","exports"],"sourceRoot":"..\\..\\..\\src","sources":["audio/audioController.ts"],"mappings":"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","ignoreList":[]}