mp-0.1.3: src/Mp/Player/GstPlayer.hs
{-
* Programmer: Piotr Borek
* E-mail: piotrborek@op.pl
* Copyright 2014 Piotr Borek
*
* Distributed under the terms of the GPL (GNU Public License)
*
* 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 2 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-}
module Mp.Player.GstPlayer (
GstPlayer(),
initGstPlayer,
gstPlayerMainLoopBlocking,
gstPlayerQuit,
gstPlayerPlay,
gstPlayerPause,
gstPlayerStop,
gstPlayerResume,
gstPlayerSetVolume,
gstPlayerGetTimeInfo,
gstPlayerTimeoutAdd,
gstPlayerAddIdle,
gstBusAddWatch,
gstPlayerSeek
) where
import System.Exit
import Control.Monad
import Data.Maybe
import Data.String.Utils
import qualified Media.Streaming.GStreamer as Gst
import qualified Media.Streaming.GStreamer.Core.Types as Gst
import qualified System.Glib.MainLoop as G
import qualified System.Glib.Properties as G
import qualified System.Glib.Signals as G
data GstPlayer = GstPlayer {
gstPlayerSource :: Gst.Element,
gstPlayerVolume :: Gst.Element,
gstPlayerPipeline :: Gst.Element,
gstPlayerMainLoop :: G.MainLoop
}
makeElement :: String -> String -> IO Gst.Element
makeElement factoryName name = do
element <- Gst.elementFactoryMake factoryName $ Just name
case element of
Just e -> return e
Nothing -> do
putStrLn $ "Could not create GStreamer \"" ++ factoryName ++ "\"element."
exitFailure
initGstPlayer :: IO GstPlayer
initGstPlayer = do
Gst.init
mainLoop <- G.mainLoopNew Nothing True
source <- makeElement "uridecodebin" "File source"
convert <- makeElement "audioconvert" "Audio converter"
volume <- makeElement "volume" "Audio volume"
sink <- makeElement "autoaudiosink" "Auto audio sink"
pipeline <- Gst.pipelineNew "pipeline"
mapM_ (Gst.binAdd $ Gst.castToBin pipeline) [source, convert, volume, sink]
_ <- Gst.elementLink convert volume
_ <- Gst.elementLink volume sink
_ <- G.on source Gst.elementPadAdded $ padAddedHandler convert
return $ GstPlayer source volume pipeline mainLoop
padAddedHandler :: Gst.Element -> Gst.Pad -> IO ()
padAddedHandler convert pad = do
maybeSinkPad <- Gst.elementGetStaticPad convert "sink"
let sinkPad = fromJust maybeSinkPad
sinkPadIsLinked <- Gst.padIsLinked sinkPad
unless sinkPadIsLinked $ do
padCaps <- Gst.padGetCaps pad
let capsStruct = fromJust $ Gst.capsGetStructure padCaps 0
let structName = Gst.structureName capsStruct
when (startswith "audio/x-raw" structName) $ Gst.padLink pad sinkPad >> return ()
gstPlayerGetTimeInfo :: GstPlayer -> IO (Maybe (Integer, Integer))
gstPlayerGetTimeInfo player = do
position <- Gst.elementQueryPosition (gstPlayerPipeline player) Gst.FormatTime
duration <- Gst.elementQueryDuration (gstPlayerPipeline player) Gst.FormatTime
if (and [isJust position, isJust duration])
then do
let (_, position') = fromJust position
let (_, duration') = fromJust duration
return $ Just (fromIntegral (position' `div` Gst.second) :: Integer, fromIntegral (duration' `div` Gst.second) :: Integer)
else
return Nothing
gstPlayerTimeoutAdd :: Int -> IO Bool -> IO ()
gstPlayerTimeoutAdd time action = flip G.timeoutAdd time action >> return ()
gstBusAddWatch :: GstPlayer -> Gst.BusFunc -> IO ()
gstBusAddWatch player action = do
bus <- Gst.pipelineGetBus (Gst.castToPipeline $ gstPlayerPipeline player)
_ <- Gst.busAddWatch bus G.priorityDefault action
return ()
gstPlayerMainLoopBlocking :: GstPlayer -> IO ()
gstPlayerMainLoopBlocking player = G.mainLoopRun $ gstPlayerMainLoop player
-- Functions below can be called from any thread
gstPlayerAddIdle :: IO Bool -> IO ()
gstPlayerAddIdle callback = G.idleAdd callback G.priorityDefaultIdle >> return ()
gstPlayerQuit :: GstPlayer -> IO ()
gstPlayerQuit player = gstPlayerAddIdle $ do
G.mainLoopQuit $ gstPlayerMainLoop player
return False
gstPlayerPlay :: GstPlayer -> FilePath -> IO ()
gstPlayerPlay player file = gstPlayerAddIdle $ do
let pipeline = gstPlayerPipeline player
let source = gstPlayerSource player
_ <- Gst.elementSetState pipeline Gst.StateNull
G.objectSetPropertyString "uri" source $ "file://" ++ file
G.objectSetPropertyInt "buffer-size" source (4 * 1024 * 1024)
_ <- Gst.elementSetState pipeline Gst.StatePlaying
return False
gstPlayerPause :: GstPlayer -> IO ()
gstPlayerPause player = gstPlayerAddIdle $ do
_ <- Gst.elementSetState (gstPlayerPipeline player) Gst.StatePaused
return False
gstPlayerStop :: GstPlayer -> IO ()
gstPlayerStop player = gstPlayerAddIdle $ do
_ <- Gst.elementSetState (gstPlayerPipeline player) Gst.StateNull
return False
gstPlayerResume :: GstPlayer -> IO ()
gstPlayerResume player = gstPlayerAddIdle $ do
_ <- Gst.elementSetState (gstPlayerPipeline player) Gst.StatePlaying
return False
gstPlayerSetVolume :: GstPlayer -> Double -> IO ()
gstPlayerSetVolume player vol = gstPlayerAddIdle $ do
let volume = gstPlayerVolume player
G.objectSetPropertyDouble "volume" volume vol
return False
gstPlayerSeek :: GstPlayer -> Integer -> IO ()
gstPlayerSeek player pos = gstPlayerAddIdle $ do
_ <- Gst.elementSeekSimple (gstPlayerPipeline player) Gst.FormatTime [Gst.SeekFlagFlush] $ fromIntegral $ (fromIntegral pos) * Gst.second
return False