packages feed

vision-0.0.2.2: src/Playtime.hs

-- -*-haskell-*-
--  Vision (for the Voice): an XMMS2 client.
--
--  Author:  Oleg Belozeorov
--  Created: 22 Jun. 2010
--
--  Copyright (C) 2010 Oleg Belozeorov
--
--  This program is free software; you can redistribute it and/or
--  modify it under the terms of the GNU General Public License as
--  published by the Free Software Foundation; either version 3 of
--  the License, or (at your option) any later version.
--
--  This program is distributed in the hope that it will be useful,
--  but WITHOUT ANY WARRANTY; without even the implied warranty of
--  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
--  General Public License for more details.
--

module Playtime
  ( initPlaytime
  , makeSeekControl
  ) where

import Control.Concurrent.MVar
import Control.Applicative
import Control.Monad
import Control.Monad.Trans

import qualified Data.Map as Map
import Data.IORef
import Data.Word
import Data.Maybe

import Graphics.UI.Gtk hiding (add, remove)

import XMMS2.Client

import XMMS
import Handler
import Medialib
import Context
import Utils
import Playback


data State
  = State { sCurrentId :: Maybe MediaId }

data Playtime
  = Playtime { pState :: MVar State
             , pAdj   :: Adjustment
             , pCId   :: ConnectId Adjustment }

state = pState context
cId   = pCId context
adj   = pAdj context

getCurrentId    = withMVar state $ return . sCurrentId
setCurrentId id = modifyMVar_ state $ \s -> return  s { sCurrentId = id }

initPlaytime = do
  updateRef <- initUpdate
  let ?updateRef = updateRef

  context <- initContext
  let ?context = context

  onServerConnectionAdd . ever $ \conn ->
    if conn
    then do
      playbackCurrentId xmms >>* handleCurrentId False
      broadcastPlaybackCurrentId xmms >>* handleCurrentId True
      playbackPlaytime xmms >>* handlePlaytime 0 False
      signalPlaybackPlaytime xmms >>* handlePlaytime 1000 True
    else do
      resetState
      setValue 0
      resetUpdate (+ 1)

  onMediaInfo . add . ever $ handleInfo
  onPlaybackStatusAdd . ever $ \status ->
    when (fromMaybe StatusStop status == StatusStop) $
      setValue 0

  return ?context


initContext = do
  state <- newMVar makeState
  adj <- adjustmentNew 0 0 bigNum 5000 5000 0
  cId <- adj `onValueChanged` do
    pos <- adjustmentGetValue adj
    seek pos

  return $ augmentContext
    Playtime { pState = state
             , pAdj   = adj
             , pCId   = cId }

makeState =
  State { sCurrentId = Nothing }

resetState =
  modifyMVar_ state $ const $ return makeState

handleCurrentId ret = do
  cid <- result
  liftIO $ do
    setCurrentId $
      if cid == 0
      then Nothing
      else Just cid
    requestInfo cid
  return ret

handleInfo (id, _, info) = do
  cid <- getCurrentId
  when (cid == Just id) $ do
    case Map.lookup "duration" info of
      Just (PropInt32 d) -> setUpper $ fromIntegral d
      _                  -> return ()

bigNum = 10000000000.0

seek pos = do
  putStrLn $ "seek to " ++ show pos
  eid <- disableUpdate
  playbackSeekMs xmms (round pos) SeekSet >>* do
    liftIO $ scheduleEnableUpdate eid
    return False
  return ()

data Update
  = Update { uEnabled   :: Bool
           , uEnablerId :: Maybe HandlerId
           , uStamp     :: Word32 }

initUpdate =
  newIORef Update { uEnabled   = True
                  , uEnablerId = Nothing
                  , uStamp     = 0 }

resetUpdate f =
  modifyIORef ?updateRef $ \up ->
    up { uEnabled   = True
       , uEnablerId = Nothing
       , uStamp     = f $ uStamp up }

updateEnabled = uEnabled <$> readIORef ?updateRef

disableUpdate = do
  up <- readIORef ?updateRef
  maybe (return ()) timeoutRemove $ uEnablerId up
  let stamp = uStamp up + 1
  writeIORef ?updateRef Update { uEnabled   = False
                               , uEnablerId = Nothing
                               , uStamp     = stamp }
  return stamp

scheduleEnableUpdate stamp = do
  eid <- (flip timeoutAdd) 500 $ do
    stamp' <- uStamp <$> readIORef ?updateRef
    when (stamp == stamp') $ resetUpdate id
    return False
  modifyIORef ?updateRef $ \up -> up { uEnablerId = Just eid }

getValue = adjustmentGetValue adj
setValue = withoutSeek . adjustmentSetValue adj
setUpper = withoutSeek . adjustmentSetUpper adj

withoutSeek =
  bracket_ (signalBlock cId) (signalUnblock cId)

handlePlaytime diff ret = do
  newPt <- catchResult 0 fromIntegral
  liftIO $ do
    en <- updateEnabled
    ps <- getPlaybackStatus
    when (en && fromMaybe StatusStop ps /= StatusStop) $ do
      oldPt <- getValue
      when (abs (newPt - oldPt) >= diff) $ setValue newPt
  return ret

makeSeekControl = do
  view <- hScaleNew adj
  scaleSetDrawValue view False
  rangeSetUpdatePolicy view UpdateContinuous
  widgetSetCanFocus view False

  id <- onPlaybackStatusAdd . ever $
    widgetSetSensitive view . (Just StatusPlay ==)
  view `onDestroy` do
    onPlaybackStatus $ remove id

  return view