vimus-0.2.0: src/PlaybackState.hs
module PlaybackState (
onChange
, elapsedTime
) where
import Prelude hiding (mapM_)
import Data.Foldable (mapM_)
import Control.Monad (when, forever)
import Control.Applicative
import Control.Monad.State.Strict (liftIO, evalStateT, gets, modify)
import Data.Default
import Data.Maybe (fromMaybe)
import qualified Network.MPD as MPD hiding (withMPD)
import Network.MPD (MPD(), Seconds, Song)
import qualified Network.MPD.Applicative as MPDA
import Timer
import Instances ()
data State = State {
timer :: Maybe Timer
, currentSong :: Maybe Song
}
instance Default State where
def = State def def
elapsedTime :: MPD.Status -> (Seconds, Seconds)
elapsedTime s = case MPD.stTime s of Just (c, t) -> (truncate c, t); _ -> (0, 0)
-- | Execute given actions on changes.
onChange :: IO () -> (Maybe Song -> IO ()) -> (Maybe Song -> MPD.Status -> IO ()) -> MPD ()
onChange plChanged songChanged statusChanged =
evalStateT (updatePlaylist >> updateTimerAndCurrentSong >> idle) def
where
-- Wait for changes.
idle = forever $ do
r <- MPD.idle [MPD.PlaylistS, MPD.PlayerS, MPD.OptionsS, MPD.UpdateS, MPD.MixerS]
if (MPD.PlaylistS `elem` r)
then do
-- If the playlist changed, we have to run both, `updatePlaylist` and
-- `updateTimerAndCurrentSong`.
--
-- `updateTimerAndCurrentSong` is necessary to emit
-- EvCurrentSongChanged. This is necessary even if the song did not
-- really change, because it's position within the playlist may have
-- changed; and we only use a position to mark the current element of
-- a ListWidget (say the currently played song here).
updatePlaylist
updateTimerAndCurrentSong
else do
-- MPD.PlayerS | MPD.OptionsS | MPD.UpdateS | MPD.MixerS
updateTimerAndCurrentSong
-- Call `plChanged` action.
updatePlaylist = liftIO plChanged
-- Query MPD's status and call `statusChanged`, and if current song changed
-- `songChanged`. If a song is currently being played, start a timer, that
-- repeatedly updates the elapsed time and calls `statusChanged`.
updateTimerAndCurrentSong = do
-- stop old timer (if any)
gets timer >>= mapM_ (liftIO . stopTimer)
modify (\st -> st {timer = Nothing})
-- query status and call `statusChanged`
(song, status) <- MPDA.runCommand $ (,) <$> MPDA.currentSong <*> MPDA.status
liftIO (statusChanged song status)
-- call `songChanged` if necessary
oldSong <- gets currentSong
when (oldSong /= song) $ do
modify (\st -> st {currentSong = song})
liftIO (songChanged song)
-- start timer, if playing
when (MPD.stState status == MPD.Playing) $ do
t0 <- liftIO getPOSIXTime
let (s0, _) = fromMaybe (0, 0) (MPD.stTime status)
t <- liftIO . startTimer t0 s0 $ \elapsed -> do
statusChanged song (updateElapsedTime status elapsed)
modify (\st -> st {timer = Just t})
-- | Set elapsed time of given status to given seconds.
updateElapsedTime :: MPD.Status -> Double -> MPD.Status
updateElapsedTime st seconds = st {MPD.stTime = Just (seconds, timeTotal)}
where
(_, timeTotal) = fromMaybe (0, 0) (MPD.stTime st)