Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Audio Service sample #316

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

ianffcs
Copy link
Contributor

@ianffcs ianffcs commented Jul 17, 2024

@ianffcs ianffcs requested review from cgrand and dupuchba as code owners July 17, 2024 19:29
@ianffcs ianffcs force-pushed the ianffcs/audio_service-sample branch from 261af05 to 2a1d419 Compare July 17, 2024 19:33
@ianffcs ianffcs force-pushed the ianffcs/audio_service-sample branch from 2a1d419 to 6928429 Compare July 18, 2024 11:31
@dupuchba
Copy link
Contributor

dupuchba commented Jul 21, 2024

some remarks:

  • don't use StreamBuilder in a sample but rather :watch (see my example bellow)
  • I really don't like introducing utility libs in the sample (basically libs that are not directly related to what the sample is showing)
  • I really don't like RX, specifically the way you declare it
:let [pipe-state (some-> audio-player
                       .-playbackEventStream
                       ^#/(da/Stream as/PlaybackState) (.map (fn [a] (prn 'CACA) (transform-event a)))
                       (.pipe (.-playbackState as-handler)))
          _add-item-to-audio-handler (.add (.-mediaItem as-handler) media-item)
          _set-audio-source-to-media-id (.setAudioSource audio-player (-> media-item
                                                                        .-id
                                                                        Uri/parse
                                                                        ja/AudioSource.uri))
          media-stream (rx/Rx.combineLatest2 (.-mediaItem as-handler)
                         (as/AudioService.position)
                         (fn [^as/MediaItem media-item
                              ^Duration position]
                           {:media-item media-item
                            :position position}))]

in a let it's going to be rebuild every time smth changes - for combineLatest, I might take time to develop smth here.

I've not finished the work, I am quite busy but it's a start

(ns sample.audio-service
  "Sample definided inside audio_service package on:
   https://github.com/ryanheise/audio_service/blob/minor/audio_service/example/lib/example_android13.dart"
  (:require ["package:flutter/material.dart" :as m]
            ["package:flutter/widgets.dart" :as w]
            ["package:audio_service/audio_service.dart" :as as]
            ["package:just_audio/just_audio.dart" :as ja]
            ["package:rxdart/rxdart.dart" :as rx]
            ["common.dart" :as c]
            ["dart:async" :as da]
            [cljd.flutter :as f]))

(def ^as/MediaItem media-item
  (as/MediaItem
    .id "https://s3.amazonaws.com/scifri-episodes/scifri20181123-episode.mp3"
    .album "Science Friday",
    .title "A Salute To Head-Scratching Science"
    .artist "Science Friday and WNYC Studios"
    .duration (Duration .milliseconds 5739820)
    .artUri (Uri/parse "https://media.wnyc.org/i/1400/1400/l/80/1/ScienceFriday_WNYCStudios_1400.jpg")))

(deftype MyAudioHandler [^ja/AudioPlayer audio-player]
  :extends (as/BaseAudioHandler)
  (play [_]
    (.play audio-player))
  (pause [_]
    (.pause audio-player))
  (stop [_]
    (.stop audio-player))
  ^:mixin as/SeekHandler
  (seek [_ ^Duration duration]
    (.seek audio-player duration)))

(defn ^:async main []
  (.ensureInitialized w/WidgetsFlutterBinding)
  (f/run
    (m/MaterialApp .title "Welcome to Flutter")
    .home
    (m/Scaffold .appBar (m/AppBar .title (m/Text "Welcome to ClojureDart")))
    .body
    m/Center
    :managed [audio-player (ja/AudioPlayer)]
    :let [transform-event
          (fn [^ja/PlaybackEvent event]
            (as/PlaybackState
              .controls [as/MediaControl.rewind
                         (if (some-> audio-player .-playing)
                           as/MediaControl.pause
                           as/MediaControl.play)
                         as/MediaControl.stop
                         as/MediaControl.fastForward]
              .systemActions #{as/MediaAction.seek
                               as/MediaAction.seekForward
                               as/MediaAction.seekBackward}
              .androidCompactActionIndices [0 1 3]
              .processingState (get {ja/ProcessingState.idle      as/AudioProcessingState.idle
                                     ja/ProcessingState.loading   as/AudioProcessingState.loading
                                     ja/ProcessingState.buffering as/AudioProcessingState.buffering
                                     ja/ProcessingState.ready     as/AudioProcessingState.ready
                                     ja/ProcessingState.completed as/AudioProcessingState.completed}
                                 (some-> audio-player
                                   .-processingState))
              .playing (-> audio-player
                         .-playing)
              .updatePosition (-> audio-player .-position)
              .bufferedPosition (-> audio-player .-bufferedPosition)
              .speed (-> audio-player .-speed)
              .queueIndex (-> event .-currentIndex)))]
    :watch [^MyAudioHandler? as-handler  (as/AudioService.init
                                           .builder (fn []  ^as/AudioHandler (->MyAudioHandler ^ja/AudioPlayer audio-player))
                                           .config (as/AudioServiceConfig
                                                     .androidNotificationChannelId "sample.audioService"
                                                     .androidNotificationChannelName "Sample Audio Service"
                                                     .androidNotificationOngoing true
                                                     .androidShowNotificationBadge true)) :dispose nil]
    :when as-handler
    :let [pipe-state (some-> audio-player
                       .-playbackEventStream
                       ^#/(da/Stream as/PlaybackState) (.map (fn [a] (prn 'CACA) (transform-event a)))
                       (.pipe (.-playbackState as-handler)))
          _add-item-to-audio-handler (.add (.-mediaItem as-handler) media-item)
          _set-audio-source-to-media-id (.setAudioSource audio-player (-> media-item
                                                                        .-id
                                                                        Uri/parse
                                                                        ja/AudioSource.uri))
          media-stream (rx/Rx.combineLatest2 (.-mediaItem as-handler)
                         (as/AudioService.position)
                         (fn [^as/MediaItem media-item
                              ^Duration position]
                           {:media-item media-item
                            :position position}))]
    :when audio-player
    (m/Column .mainAxisAlignment m/MainAxisAlignment.center)
    .children
    [(f/widget
       :watch [media-item (.-mediaItem as-handler)]
       :when media-item
       (m/Text (.-title ^as/MediaItem media-item)))
     (f/widget
       :watch [playback-state (.-playbackState as-handler) #_(stream (map dedupe) (.-playbackState as-handler))]
       :let [playing (some-> playback-state .-playing)]
       (m/Row .mainAxisAlignment m/MainAxisAlignment.center)
       .children
       [(m/IconButton
          .icon (m/Icon m/Icons.fast_rewind)
          .iconSize 64
          .onPressed (fn [] (.rewind ^MyAudioHandler as-handler) nil))
        (m/IconButton
          .icon (m/Icon (if playing m/Icons.pause m/Icons.play_arrow))
          .iconSize 64
          .onPressed (fn []
                       (if playing
                         (.pause ^MyAudioHandler as-handler)
                         (.play ^MyAudioHandler as-handler))
                       nil))
        (m/IconButton
          .icon (m/Icon m/Icons.stop)
          .iconSize 64
          .onPressed (fn []
                       (.stop ^MyAudioHandler as-handler)
                       nil))
        (m/IconButton
          .icon (m/Icon m/Icons.fast_forward)
          .iconSize 64
          .onPressed (fn []
                       (.fastForward ^MyAudioHandler as-handler)
                       nil))])

     (f/widget
       :watch [ms media-stream]
       :when ms
       (c/SeekBar
         .duration (or (some-> ^as/MediaItem (get ms :media-item) .-duration) Duration/zero)
         .position (or (some-> ms :position) Duration/zero)
         .onChangeEnd (fn [new-pos]
                        (.seek ^as/SeekHandler as-handler new-pos)
                        nil)))

     (f/widget
       :watch [ps (some-> ^as/AudioHandler as-handler
                    .-playbackState
                    (.map (fn [st]
                            (.-playing ^as/PlaybackState st)))
                    (.distinct))]
       (m/Text (str "Processing state: " (or ps as/AudioProcessingState.idle))))]))

@cgrand
Copy link
Contributor

cgrand commented Jul 25, 2024

Hey @ianffcs thank you for this work. However I think it's better for samples to focus on one thing at a time so that the reader doesn't have to grok several concepts/libs at once. Here several unrelated packages are used. Would it be possible to reduce the scope?

@ianffcs
Copy link
Contributor Author

ianffcs commented Jul 25, 2024

I can't think on how I can reduce the scope, because it's a sample about having an audio player that plays on background. This is the minimal example that I've found around for doing this.

This is very useful for anyone that wants to implement this audio-background feature in any app.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants