packages feed

mpvguihs-0.1.1: MpvPlayer/Backend.hs

module MpvPlayer.Backend where

import Data.List
import Data.Word
import Data.IORef
import Data.Maybe
import System.Process
import System.IO
import System.Posix.IO
import System.Directory
import Text.Printf
import Text.Read
import Control.Concurrent 
import Control.Monad
import Control.Exception

data PlayStatus = PlayStatus {
      playStatusPaused :: Bool,
      playStatusLength :: Double,
      playStatusPos :: Double,
      playStatusMuted :: Bool,
      playStatusVol :: Int,
      playStatusFullscreen :: Bool
} deriving (Show)

data Player = Player {
      playerWid :: Word32,

      playerProcess :: ProcessHandle,
      playerCmdIn :: Handle,
      playerCmdInFilePath :: FilePath,

      playerOutWrite :: Handle,
      playerOutRead :: Handle
}

data PlayerDead = PlayerDead

cmdPrefix :: String
cmdPrefix = "mpvguihs command response: "

buildArgs :: Word32 -> FilePath -> FilePath -> String -> [String]
buildArgs wid fifo filename extraArgs = filter (not . null) $ 
  ["--wid=" ++ (printf "0x%x" wid), 
   "--input-file=" ++ fifo, 
   "--status-msg=",
   extraArgs,
   filename]

parseLines :: Maybe PlayStatus -> Handle -> IO (Maybe PlayStatus)
parseLines ps readHandle = do
  ready <- hReady readHandle
  if ready
    then do
      line <- hGetLine readHandle 
      let status = stripPrefix cmdPrefix line
      case status of
        Nothing -> putStrLn line >> parseLines ps readHandle
        Just i  -> 
          let [len, r, p, m, vol, fs] = words i
              len' = fromMaybe 0.0 $ readMaybe len
              r' = fromMaybe 0.0 $ readMaybe r
              paused = p == "yes"
              muted = m == "yes"
              vol' = round $ (fromMaybe 0.0 $ readMaybe vol :: Double)
              fullscreen = fs == "yes"
          in parseLines (Just $ PlayStatus paused 
                                           len' 
                                           r' 
                                           muted 
                                           vol' 
                                           fullscreen) readHandle
    else return ps

hPutStrLnSafe :: Handle -> String -> IO ()
hPutStrLnSafe h str = catch (hPutStrLn h str) ignoreException
  where ignoreException :: SomeException -> IO ()
        ignoreException _ = return ()

mpvGetPlayStatus :: IORef Player -> IO (Either PlayerDead (Maybe PlayStatus))
mpvGetPlayStatus playerRef = do
  p <- readIORef playerRef

  catch (do hPutStrLn (playerCmdIn p) $ concat 
              ["print_text \"", cmdPrefix,
               "${=length} ${=time-pos} ${pause} " ++
               "${mute} ${volume} ${fullscreen}\""]
    
            ps <- catch (parseLines Nothing $ playerOutRead p) $ printException
            return $ Right ps)
        handleNoPlayerException

  where printException :: SomeException -> IO (Maybe PlayStatus)
        printException e = print e >> return Nothing
        handleNoPlayerException :: SomeException 
                                -> IO (Either PlayerDead (Maybe PlayStatus))
        handleNoPlayerException _ = return $ Left PlayerDead

mpvPlay :: Word32 -> FilePath -> String -> IO (IORef Player)
mpvPlay wid filename extraArgs = do
  tmpDir <- getTemporaryDirectory

  (cmdFile, cmdHandle) <- openTempFile tmpDir "mpvguihs"
  hClose cmdHandle         
  removeFile cmdFile
  system $ "mkfifo " ++ cmdFile

  let args = buildArgs wid cmdFile filename extraArgs
  putStrLn $ "Launching mpv with args: " ++ show args
  
  (readFd, writeFd) <- createPipe
  outWrite <- fdToHandle writeFd
  outRead <- fdToHandle readFd
  
  errWrite <- openFile "/dev/null" WriteMode

  process <- runProcess "mpv" args 
             Nothing Nothing Nothing (Just outWrite) (Just errWrite)

  cmdFd <- openFd cmdFile WriteOnly Nothing defaultFileFlags
  cmdHandle' <- fdToHandle cmdFd
  hSetBuffering cmdHandle' NoBuffering

  newIORef $ Player wid process cmdHandle' cmdFile outWrite outRead

mpvPause :: IORef Player -> IO ()
mpvPause playerRef = do
  player <- readIORef playerRef
  status <- mpvGetPlayStatus playerRef
  case status of
    Left _         -> return ()
    Right Nothing  -> mpvPause playerRef
    Right (Just s) -> when (not $ playStatusPaused s) $ 
                        hPutStrLnSafe (playerCmdIn player) "cycle pause"
  
mpvUnpause :: IORef Player -> IO ()
mpvUnpause playerRef = do
  player <- readIORef playerRef
  status <- mpvGetPlayStatus playerRef
  case status of 
    Left _         -> return ()
    Right Nothing  -> mpvUnpause playerRef
    Right (Just s) -> when (playStatusPaused s) $ 
                        hPutStrLnSafe (playerCmdIn player) "cycle pause"

mpvSeek :: IORef Player -> Double -> IO ()
mpvSeek player ratio = do
  p <- readIORef player
  let percent = ratio * 100
  hPutStrLnSafe (playerCmdIn p) $ printf "seek %f absolute-percent" percent

mpvStop :: IORef Player -> IO ()
mpvStop playerRef = do
  p <- readIORef playerRef
  hPutStrLnSafe (playerCmdIn p) "stop"

mpvSetMuted :: IORef Player -> Bool -> IO ()
mpvSetMuted playerRef val = do
  p <- readIORef playerRef
  let val' = if val then "yes" else "no"
  hPutStrLnSafe (playerCmdIn p) $ "set mute " ++ val'

mpvSetVolume :: IORef Player -> Int -> IO ()
mpvSetVolume playerRef vol = do
  p <- readIORef playerRef
  hPutStrLnSafe (playerCmdIn p) $ "set volume " ++ show vol

mpvToggleFullscreen :: IORef Player -> IO ()
mpvToggleFullscreen playerRef = do
  p <- readIORef playerRef
  hPutStrLnSafe (playerCmdIn p) $ "cycle fullscreen"
  
mpvTerminate :: IORef Player -> IO ()
mpvTerminate playerRef = do
  p <- readIORef playerRef
  let h = playerCmdIn p
  hPutStrLnSafe h "quit 0"

  hClose h 
  hClose $ playerOutRead p
  hClose $ playerOutWrite p

  removeFile $ playerCmdInFilePath p

  threadDelay 250000
  terminateProcess (playerProcess p)