playback_control.c 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. /**
  2. * Source-agnostic playback controller.
  3. *
  4. * For AirPlay:
  5. * - Play/Pause: mute/unmute the DAC locally. If the source sent
  6. * DACP headers (AirPlay 1), we also forward the command via DACP
  7. * so the source UI updates. Modern iOS AirPlay 2 does not send
  8. * DACP headers, so local mute is the only option.
  9. * - Next/Prev: forwarded via DACP when available (AirPlay 1 only).
  10. * - Volume: adjusted locally (DAC + NVS persistence) and mirrored
  11. * to the source via DACP when available.
  12. *
  13. * For Bluetooth:
  14. * - All commands sent as AVRCP passthrough to source device
  15. * - Source device controls playback and sends volume back
  16. */
  17. #include "playback_control.h"
  18. #include "dac.h"
  19. #include "dacp_client.h"
  20. #include "rtsp_events.h"
  21. #include "rtsp_server.h"
  22. #include "settings.h"
  23. #include "esp_log.h"
  24. #ifdef CONFIG_BT_A2DP_ENABLE
  25. #include "a2dp_sink.h"
  26. #endif
  27. static const char *TAG = "playback_ctrl";
  28. #define VOLUME_STEP_DB 3.0f
  29. #define VOLUME_MIN_DB (-30.0f)
  30. #define VOLUME_MAX_DB 0.0f
  31. static playback_source_t s_source = PLAYBACK_SOURCE_NONE;
  32. static bool s_muted = false;
  33. static float s_pre_mute_db = -15.0f;
  34. esp_err_t playback_control_init(void) {
  35. dacp_init();
  36. ESP_LOGI(TAG, "Playback control initialized");
  37. return ESP_OK;
  38. }
  39. void playback_control_set_source(playback_source_t source) {
  40. s_muted = false;
  41. s_source = source;
  42. ESP_LOGI(TAG, "Source set to %d", source);
  43. }
  44. playback_source_t playback_control_get_source(void) {
  45. return s_source;
  46. }
  47. // ============================================================================
  48. // AirPlay local volume helpers
  49. // ============================================================================
  50. static float clamp_volume(float db) {
  51. if (db < VOLUME_MIN_DB) {
  52. return VOLUME_MIN_DB;
  53. }
  54. if (db > VOLUME_MAX_DB) {
  55. return VOLUME_MAX_DB;
  56. }
  57. return db;
  58. }
  59. // Convert AirPlay dB (-30..0) to DACP percent (0..100)
  60. static float db_to_dacp_percent(float db) {
  61. if (db <= VOLUME_MIN_DB) {
  62. return 0.0f;
  63. }
  64. if (db >= VOLUME_MAX_DB) {
  65. return 100.0f;
  66. }
  67. return ((db - VOLUME_MIN_DB) / (VOLUME_MAX_DB - VOLUME_MIN_DB)) * 100.0f;
  68. }
  69. static void airplay_adjust_volume(float step_db) {
  70. float current_db;
  71. if (settings_get_volume(&current_db) != ESP_OK) {
  72. current_db = -15.0f; // default 50 %
  73. }
  74. float new_db = clamp_volume(current_db + step_db);
  75. airplay_set_volume(new_db);
  76. if (s_muted) {
  77. // Update saved level so unmute restores the new volume
  78. s_pre_mute_db = new_db;
  79. } else {
  80. dac_set_volume(new_db);
  81. }
  82. ESP_LOGI(TAG, "AirPlay volume: %.1f -> %.1f dB%s", current_db, new_db,
  83. s_muted ? " (muted)" : "");
  84. // Notify AirPlay client via DACP (best-effort, don't block local action)
  85. dacp_send_volume(db_to_dacp_percent(new_db));
  86. }
  87. // ============================================================================
  88. // Public API
  89. // ============================================================================
  90. void playback_control_play_pause(void) {
  91. switch (s_source) {
  92. case PLAYBACK_SOURCE_AIRPLAY: {
  93. if (dacp_is_active()) {
  94. // Tell the source to toggle playback — it will FLUSH the stream
  95. // on pause and RECORD on resume, so we don't need local muting.
  96. // Signal the v1 grace period loop (if active) so it sends the
  97. // DACP command at the right time and keeps waiting for reconnect.
  98. // If not in a grace period, the flag is harmless.
  99. rtsp_server_request_resume();
  100. dacp_send_playpause();
  101. ESP_LOGI(TAG, "AirPlay play/pause sent via DACP");
  102. } else {
  103. // Fallback: mute/unmute the DAC locally when no DACP session
  104. if (!s_muted) {
  105. if (settings_get_volume(&s_pre_mute_db) != ESP_OK) {
  106. s_pre_mute_db = -15.0f; // default 50 %
  107. }
  108. dac_set_volume(VOLUME_MIN_DB);
  109. s_muted = true;
  110. rtsp_events_emit(RTSP_EVENT_PAUSED, NULL);
  111. ESP_LOGI(TAG, "AirPlay muted locally (was %.1f dB)", s_pre_mute_db);
  112. } else {
  113. dac_set_volume(s_pre_mute_db);
  114. s_muted = false;
  115. rtsp_events_emit(RTSP_EVENT_PLAYING, NULL);
  116. ESP_LOGI(TAG, "AirPlay unmuted locally (%.1f dB)", s_pre_mute_db);
  117. }
  118. }
  119. break;
  120. }
  121. #ifdef CONFIG_BT_A2DP_ENABLE
  122. case PLAYBACK_SOURCE_BLUETOOTH:
  123. bt_a2dp_send_playpause();
  124. break;
  125. #endif
  126. default:
  127. ESP_LOGI(TAG, "Play/pause: no active source (source=%d)", s_source);
  128. break;
  129. }
  130. }
  131. void playback_control_volume_up(void) {
  132. switch (s_source) {
  133. case PLAYBACK_SOURCE_AIRPLAY:
  134. airplay_adjust_volume(VOLUME_STEP_DB);
  135. break;
  136. #ifdef CONFIG_BT_A2DP_ENABLE
  137. case PLAYBACK_SOURCE_BLUETOOTH:
  138. bt_a2dp_send_volume_up();
  139. break;
  140. #endif
  141. default:
  142. break;
  143. }
  144. }
  145. void playback_control_volume_down(void) {
  146. switch (s_source) {
  147. case PLAYBACK_SOURCE_AIRPLAY:
  148. airplay_adjust_volume(-VOLUME_STEP_DB);
  149. break;
  150. #ifdef CONFIG_BT_A2DP_ENABLE
  151. case PLAYBACK_SOURCE_BLUETOOTH:
  152. bt_a2dp_send_volume_down();
  153. break;
  154. #endif
  155. default:
  156. break;
  157. }
  158. }
  159. void playback_control_next(void) {
  160. switch (s_source) {
  161. case PLAYBACK_SOURCE_AIRPLAY:
  162. dacp_send_next();
  163. ESP_LOGI(TAG, "AirPlay next track via DACP");
  164. break;
  165. #ifdef CONFIG_BT_A2DP_ENABLE
  166. case PLAYBACK_SOURCE_BLUETOOTH:
  167. bt_a2dp_send_next();
  168. break;
  169. #endif
  170. default:
  171. break;
  172. }
  173. }
  174. void playback_control_prev(void) {
  175. switch (s_source) {
  176. case PLAYBACK_SOURCE_AIRPLAY:
  177. dacp_send_prev();
  178. ESP_LOGI(TAG, "AirPlay prev track via DACP");
  179. break;
  180. #ifdef CONFIG_BT_A2DP_ENABLE
  181. case PLAYBACK_SOURCE_BLUETOOTH:
  182. bt_a2dp_send_prev();
  183. break;
  184. #endif
  185. default:
  186. break;
  187. }
  188. }
  189. void playback_control_toggle_mute(void) {
  190. switch (s_source) {
  191. case PLAYBACK_SOURCE_AIRPLAY:
  192. if (!s_muted) {
  193. if (settings_get_volume(&s_pre_mute_db) != ESP_OK) {
  194. s_pre_mute_db = -15.0f; // default 50 %
  195. }
  196. dac_set_volume(VOLUME_MIN_DB);
  197. s_muted = true;
  198. rtsp_events_emit(RTSP_EVENT_PAUSED, NULL);
  199. ESP_LOGI(TAG, "AirPlay muted locally (was %.1f dB)", s_pre_mute_db);
  200. } else {
  201. dac_set_volume(s_pre_mute_db);
  202. s_muted = false;
  203. rtsp_events_emit(RTSP_EVENT_PLAYING, NULL);
  204. ESP_LOGI(TAG, "AirPlay unmuted locally (%.1f dB)", s_pre_mute_db);
  205. }
  206. break;
  207. #ifdef CONFIG_BT_A2DP_ENABLE
  208. case PLAYBACK_SOURCE_BLUETOOTH:
  209. // Bluetooth uses AVRCP absolute volume — no dedicated mute. Log.
  210. ESP_LOGI(TAG, "Bluetooth: mute toggle not supported (use source device)");
  211. break;
  212. #endif
  213. default:
  214. ESP_LOGI(TAG, "Toggle mute: no active source (source=%d)", s_source);
  215. break;
  216. }
  217. }
  218. bool playback_control_is_muted(void) {
  219. return s_muted;
  220. }
  221. int playback_control_get_volume_percent(void) {
  222. if (s_muted) {
  223. return 0;
  224. }
  225. float db;
  226. if (settings_get_volume(&db) != ESP_OK) {
  227. db = -15.0f;
  228. }
  229. return (int)(db_to_dacp_percent(clamp_volume(db)) + 0.5f);
  230. }
  231. void playback_control_set_volume_percent(int percent) {
  232. if (percent < 0) percent = 0;
  233. if (percent > 100) percent = 100;
  234. ESP_LOGD(TAG, "音量同步: %d%% (当前源=%d)", percent, s_source);
  235. switch (s_source) {
  236. case PLAYBACK_SOURCE_AIRPLAY: {
  237. float db = VOLUME_MIN_DB + (percent / 100.0f) * (VOLUME_MAX_DB - VOLUME_MIN_DB);
  238. db = clamp_volume(db);
  239. ESP_LOGD(TAG, "AirPlay 同步 - DB: %.2f", db);
  240. airplay_set_volume(db);
  241. ESP_LOGD(TAG, "AirPlay 同步 - airplay_set_volume 执行后");
  242. settings_set_volume(db);
  243. ESP_LOGD(TAG, "AirPlay 同步 - settings_set_volume 执行后");
  244. if (s_muted) {
  245. s_pre_mute_db = db;
  246. } else {
  247. dac_set_volume(db);
  248. }
  249. ESP_LOGD(TAG, "AirPlay 同步 - dac_set_volume 执行后");
  250. dacp_send_volume(percent);
  251. ESP_LOGD(TAG, "AirPlay 同步 - dacp_send_volume 执行后");
  252. break;
  253. }
  254. #ifdef CONFIG_BT_A2DP_ENABLE
  255. case PLAYBACK_SOURCE_BLUETOOTH: {
  256. uint8_t bt_vol = (uint8_t)(percent * 127 / 100);
  257. ESP_LOGI(TAG, "蓝牙同步 - 音量: %d/127", bt_vol);
  258. bt_a2dp_set_volume(bt_vol);
  259. ESP_LOGI(TAG, "蓝牙同步 - bt_a2dp_set_volume 执行后");
  260. break;
  261. }
  262. #endif
  263. default:
  264. ESP_LOGI(TAG, "无活跃音频源,不执行音量同步");
  265. break;
  266. }
  267. }