packages feed

vision-0.0.5.0: 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.
--

{-# LANGUAGE DeriveDataTypeable, UndecidableInstances #-}

module Playtime
  ( initPlaytime
  , makeSeekControl
  , PlaytimeM
  , playtimeEnv
  ) where

import Control.Concurrent
import Control.Concurrent.STM
import Control.Concurrent.STM.TWatch
import Control.Concurrent.STM.TGVar

import Control.Applicative
import Control.Monad
import Control.Monad.Trans
import Control.Monad.State
import Control.Monad.EnvIO

import Data.Maybe
import qualified Data.Map as Map
import Data.Typeable
import Data.Env

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

import XMMS2.Client hiding (playbackStatus)

import XMMS
import Medialib
import Context hiding (makeContext)
import Utils
import Playback
import Registry


data Playtime
  = Playtime { pAdj        :: Adjustment
             , pCurrentIdV :: TVar (Maybe MediaId)
             , pPlaytimeV  :: TVar Int
             , pSeekCountV :: TVar (Maybe Int)
             }
    deriving (Typeable)

adj        = pAdj context
currentIdV = pCurrentIdV context
playtimeV  = pPlaytimeV context
seekCountV = pSeekCountV context


data Ix = Ix deriving (Typeable)

class    (EnvM Ix Playtime m) => PlaytimeM m
instance (EnvM Ix Playtime m) => PlaytimeM m

playtimeEnv :: Extract Ix Playtime
playtimeEnv = Extract

initPlaytime = do
  pt <- liftIO $ makeContext
  let ?context = augmentContext pt
  addEnv Ix pt

  liftIO $ do
    cId <- setupSeek
    xcW <- atomically $ newTGWatch connectedV
    let mon xc
          | xc = do
            rt <- forkIO requestPlaytime
            ct <- forkIO $ evalPTM cId dispatch
            xc <- atomically $ watch xcW
            killThread rt
            killThread ct
            atomically $ writeTVar seekCountV Nothing
            mon xc
          | otherwise = do
            xc <- atomically $ watch xcW
            mon xc
    forkIO $ mon False

  return ()


makeContext = do
  adj        <- adjustmentNew 0 0 0 5000 5000 0
  currentIdV <- newTVarIO Nothing
  playtimeV  <- newTVarIO 0
  seekCountV <- newTVarIO Nothing
  return $ Playtime { pAdj        = adj
                    , pCurrentIdV = currentIdV
                    , pPlaytimeV  = playtimeV
                    , pSeekCountV = seekCountV
                    }

requestPlaytime = forever $ do
  checkSeekCount
  playbackPlaytime xmms >>* do
    pt <- catchResult 0 fromIntegral
    liftIO $ atomically $ writeTVar playtimeV pt
  threadDelay 1000000

checkSeekCount = atomically $ do
  rc <- readTVar seekCountV
  if rc == Just 0
    then return ()
    else retry

modSeekCount op = atomically $ do
  rc <- readTVar seekCountV
  withJust rc $ \rc ->
    writeTVar seekCountV $ Just $ op rc

handleCurrentId = do
  ci <- catchResult Nothing Just
  liftIO $ atomically $ writeTVar currentIdV ci

setupSeek =  adj `onValueChanged` do
  modSeekCount (+ 1)
  v <- adjustmentGetValue adj
  playbackSeekMs xmms (round v) SeekSet >>* do
    liftIO $ modSeekCount $ \n -> n - 1

makeSeekControl = do
  adj <- envsx Ix pAdj
  liftIO $ do
    view <- hScaleNew adj
    scaleSetDrawValue view False
    rangeSetUpdatePolicy view UpdateContinuous
    widgetSetCanFocus view False

    w <- atomically $ newEmptyTWatch playbackStatus
    t <- forkIO $ forever $ do
      s <- atomically $ watch w
      widgetSetSensitive view $ s == (Just StatusPlay)

    view `onDestroy` (killThread t)

    return view


data S =
  S { sPt :: Int
    , sId :: Maybe MediaId
    , sTd :: Int
    , sPs :: PlaybackStatus
    , sCi :: ConnectId Adjustment
    }

mkS id cId =
  S { sPt = 0
    , sId = id
    , sTd = 0
    , sPs = StatusStop
    , sCi = cId
    }

evalPTM cId f =
  evalStateT f (mkS Nothing cId)

withoutSeek f = do
  cId <- gets sCi
  liftIO $ bracket_
    (signalBlock cId)
    (signalUnblock cId)
    (liftIO f)

data Msg
  = PT Int
  | CI (Maybe MediaId)
  | MI (MediaId, Stamp, MediaInfo)
  | PS (Maybe PlaybackStatus)

dispatch = do
  (miC, ciW, psW, ptW) <- liftIO $ do
    playbackCurrentId xmms >>*
      handleCurrentId
    broadcastPlaybackCurrentId xmms >>*
      (handleCurrentId >> persist)
    atomically $ writeTVar seekCountV $ Just 0
    miC <- atomically $ dupTChan mediaInfoChan
    ciW <- atomically $ newTWatch currentIdV Nothing
    psW <- atomically $ newEmptyTWatch playbackStatus
    ptW <- atomically $ newEmptyTWatch playtimeV
    return (miC, ciW, psW, ptW)
  forever $ do
    msg <- liftIO $ atomically $
           msum [ PS <$> watch psW
                , CI <$> watch ciW
                , PT <$> watch ptW
                , MI <$> readTChan miC
                ]
    case msg of
      PT pt -> handlePT pt
      CI ci -> handleCI ci
      MI mi -> handleMI mi
      PS ps -> handlePS $ fromMaybe StatusStop ps

handlePT pt = do
  s <- get
  unless (sPs s == StatusStop) $ do
    put s { sPt = pt }
    unless (sTd s == 0) $
      withoutSeek $ adjustmentSetValue adj $ fromIntegral pt

handleCI id = do
  modify $ \s ->
    s { sPt = 0
      , sId = id
      , sTd = 0
      }
  withoutSeek $ do
    adjustmentSetValue adj 0
    adjustmentSetUpper adj 0
  liftIO $ withJust id $ requestInfo Current

handleMI (id, _, info) = do
  s <- get
  when (sId s == Just id) $
    case Map.lookup "duration" info of
      Just (PropInt32 d) -> do
        put s { sTd = fromIntegral d }
        withoutSeek $ do
          adjustmentSetUpper adj $ fromIntegral d
          unless (sPs s == StatusStop) $
            adjustmentSetValue adj $ fromIntegral $ sPt s
      _ -> return ()

handlePS StatusStop = do
  modify $ \s ->
    s { sPs = StatusStop
      , sPt = 0
      }
  withoutSeek $ adjustmentSetValue adj 0
handlePS ps =
  modify $ \s -> s { sPs = ps }