diff options
author | Maniac <na@na.na> | 2015-07-03 22:29:41 +0200 |
---|---|---|
committer | Maniac <na@na.na> | 2015-07-03 22:29:41 +0200 |
commit | da407909879f82c15f786bda3fedda0cae892100 (patch) | |
tree | 143c2b842028c38e8f7facfe576567039459afd9 /player.c | |
download | vdr-plugin-mpv-0.0.4.tar.gz vdr-plugin-mpv-0.0.4.tar.bz2 |
import 0.0.40.0.4
Diffstat (limited to 'player.c')
-rw-r--r-- | player.c | 570 |
1 files changed, 570 insertions, 0 deletions
diff --git a/player.c b/player.c new file mode 100644 index 0000000..166cfcc --- /dev/null +++ b/player.c @@ -0,0 +1,570 @@ +////////////////////////////////////////////////////////////////////////////// +/// /// +/// This file is part of the VDR mpv plugin and licensed under AGPLv3 /// +/// /// +/// See the README file for copyright information /// +/// /// +////////////////////////////////////////////////////////////////////////////// + +#include <locale.h> +#include <string> +#include <vector> + +#include "player.h" +#include "config.h" +#include "osd.h" + +#ifdef USE_XRANDR +#include <X11/extensions/Xrandr.h> +#endif + +using std::vector; + +#define MPV_OBSERVE_TIME_POS 1 +#define MPV_OBSERVE_DISC_MENU 2 +#define MPV_OBSERVE_FPS 3 +#define MPV_OBSERVE_FILENAME 4 +#define MPV_OBSERVE_LENGTH 5 +#define MPV_OBSERVE_CHAPTERS 6 +#define MPV_OBSERVE_CHAPTER 7 +#define MPV_OBSERVE_PAUSE 8 +#define MPV_OBSERVE_SPEED 9 +#define MPV_OBSERVE_MEDIA_TITLE 10 + +volatile int cMpvPlayer::running = 0; + +// check mpv errors and send them to log +static inline void check_error(int status) +{ + if (status < 0) + { + esyslog("[mpv] API error: %s\n", mpv_error_string(status)); + } +} + +void *cMpvPlayer::ObserverThread(void *handle) +{ + cMpvPlayer *Player = (cMpvPlayer*) handle; + struct mpv_event_log_message *msg; + + // set properties which should be observed + mpv_observe_property(Player->hMpv, MPV_OBSERVE_TIME_POS, "time-pos", MPV_FORMAT_DOUBLE); + mpv_observe_property(Player->hMpv, MPV_OBSERVE_DISC_MENU, "disc-menu-active", MPV_FORMAT_FLAG); + mpv_observe_property(Player->hMpv, MPV_OBSERVE_FPS, "fps", MPV_FORMAT_DOUBLE); + mpv_observe_property(Player->hMpv, MPV_OBSERVE_FILENAME, "filename", MPV_FORMAT_STRING); + mpv_observe_property(Player->hMpv, MPV_OBSERVE_LENGTH, "length", MPV_FORMAT_DOUBLE); + mpv_observe_property(Player->hMpv, MPV_OBSERVE_CHAPTERS, "chapters", MPV_FORMAT_INT64); + mpv_observe_property(Player->hMpv, MPV_OBSERVE_CHAPTER, "chapter", MPV_FORMAT_INT64); + mpv_observe_property(Player->hMpv, MPV_OBSERVE_PAUSE, "pause", MPV_FORMAT_FLAG); + mpv_observe_property(Player->hMpv, MPV_OBSERVE_SPEED, "speed", MPV_FORMAT_DOUBLE); + mpv_observe_property(Player->hMpv, MPV_OBSERVE_MEDIA_TITLE, "media-title", MPV_FORMAT_STRING); + + while (Player->PlayerIsRunning()) + { + mpv_event *event = mpv_wait_event(Player->hMpv, 5); + switch (event->event_id) + { + case MPV_EVENT_SHUTDOWN : + Player->running = 0; + break; + + case MPV_EVENT_PROPERTY_CHANGE : + Player->HandlePropertyChange(event); + break; + + case MPV_EVENT_PLAYBACK_RESTART : + Player->ChangeFrameRate(Player->CurrentFps()); // switching directly after the fps event causes black screen + break; + + case MPV_EVENT_LOG_MESSAGE : + msg = (struct mpv_event_log_message *)event->data; + // without DEBUG log to error since we only request error messages from mpv in this case + #ifdef DEBUG + dsyslog("[mpv]: %s\n", msg->text); + #else + esyslog("[mpv]: %s\n", msg->text); + #endif + break; + + case MPV_EVENT_NONE : + if (!Player->IsPaused()) + { + // no event since 5 secons and not paused -> player died + Player->running = 0; + } + break; + + case MPV_EVENT_TRACKS_CHANGED : + Player->HandleTracksChange(); + break; + + case MPV_EVENT_END_FILE : + case MPV_EVENT_PAUSE : + case MPV_EVENT_UNPAUSE : + case MPV_EVENT_FILE_LOADED : + case MPV_EVENT_VIDEO_RECONFIG : + case MPV_EVENT_GET_PROPERTY_REPLY : + case MPV_EVENT_SET_PROPERTY_REPLY : + case MPV_EVENT_COMMAND_REPLY : + case MPV_EVENT_START_FILE : + case MPV_EVENT_TRACK_SWITCHED : + case MPV_EVENT_IDLE : + case MPV_EVENT_TICK : + case MPV_EVENT_SCRIPT_INPUT_DISPATCH : + case MPV_EVENT_CLIENT_MESSAGE : + case MPV_EVENT_AUDIO_RECONFIG : + case MPV_EVENT_METADATA_UPDATE : + case MPV_EVENT_SEEK : + case MPV_EVENT_CHAPTER_CHANGE : + default : + dsyslog("[mpv]: event: %d %s\n", event->event_id, mpv_event_name(event->event_id)); + break; + } + } + dsyslog("[mpv] Observer thread ended\n"); + return handle; +} + +cMpvPlayer::cMpvPlayer(string Filename, bool Shuffle) +:cPlayer(pmExtern_THIS_SHOULD_BE_AVOIDED) +{ + PlayFilename = Filename; + PlayShuffle = Shuffle; + running = 0; + OriginalFps = -1; +} + +cMpvPlayer::~cMpvPlayer() +{ + dsyslog("[mpv]%s: end\n", __FUNCTION__); + Detach(); +} + +void cMpvPlayer::Activate(bool on) +{ + if (on) + PlayerStart(); +} + +void cMpvPlayer::SetAudioTrack(eTrackType Type, const tTrackId *TrackId) +{ + SetAudio(TrackId->id); +} + +void cMpvPlayer::SetSubtitleTrack(eTrackType Type, const tTrackId *TrackId) +{ + if (Type == ttNone) + { + check_error(mpv_set_option_string(hMpv, "sub-forced-only", "yes")); + return; + } + check_error(mpv_set_option_string(hMpv, "sub-forced-only", "no")); + SetSubtitle(TrackId->id); +} + +void cMpvPlayer::PlayerStart() +{ + PlayerPaused = 0; + PlayerSpeed = 1; + PlayerDiscNav = 0; + + SwitchOsdToMpv(); + + //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! WARNING !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // we are cheating here with mpv since it checks for LC_NUMERIC=C at startup + // this can cause unforseen issues with mpv + //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! WARNING !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + std::string LocaleSave = setlocale(LC_NUMERIC, NULL); + dsyslog ("get locale %s\n", LocaleSave.c_str()); + setlocale(LC_NUMERIC, "C"); + hMpv = mpv_create(); + if (!hMpv) + { + esyslog("[mpv] failed to create context\n"); + cControl::Shutdown(); + } + + int64_t osdlevel = 0; + + check_error(mpv_set_option_string(hMpv, "vo", MpvPluginConfig->VideoOut.c_str())); + check_error(mpv_set_option_string(hMpv, "hwdec", MpvPluginConfig->HwDec.c_str())); + check_error(mpv_set_option_string(hMpv, "ao", MpvPluginConfig->AudioOut.c_str())); + check_error(mpv_set_option_string(hMpv, "hwdec-codecs", MpvPluginConfig->HwDec.c_str())); + check_error(mpv_set_option_string(hMpv, "slang", MpvPluginConfig->Languages.c_str())); // this can break br menu display + check_error(mpv_set_option_string(hMpv, "alang", MpvPluginConfig->Languages.c_str())); + check_error(mpv_set_option_string(hMpv, "cache", "no")); // video stutters if enabled + check_error(mpv_set_option_string(hMpv, "fullscreen", "yes")); + check_error(mpv_set_option_string(hMpv, "sub-forced-only", "yes")); + check_error(mpv_set_option_string(hMpv, "ontop", "yes")); + check_error(mpv_set_option_string(hMpv, "cursor-autohide", "always")); + check_error(mpv_set_option_string(hMpv, "stop-playback-on-init-failure", "no")); + check_error(mpv_set_option(hMpv, "osd-level", MPV_FORMAT_INT64, &osdlevel)); + + if (MpvPluginConfig->UsePassthrough) + { + check_error(mpv_set_option_string(hMpv, "ad", "-spdif:mp3,-spdif:aac,spdif:*")); + if (MpvPluginConfig->UseDtsHdPassthrough) + check_error(mpv_set_option_string(hMpv, "ad-spdif-dtshd", "yes")); + } + else + { + int64_t StartVolume = cDevice::CurrentVolume() / 2.55; + check_error(mpv_set_option(hMpv, "volume", MPV_FORMAT_INT64, &StartVolume)); + if (MpvPluginConfig->StereoDownmix) + { + check_error(mpv_set_option_string(hMpv, "ad-lavc-downmix", "yes")); + check_error(mpv_set_option_string(hMpv, "audio-channels", "2")); + } + } + + if (PlayShuffle && IsPlaylist(PlayFilename)) + { + dsyslog("use shuffle\n"); + check_error(mpv_set_option_string(hMpv, "shuffle", "yes")); + } + +#ifdef DEBUG + mpv_request_log_messages(hMpv, "info"); +#else + mpv_request_log_messages(hMpv, "error"); +#endif + + if (mpv_initialize(hMpv) < 0) + { + esyslog("[mpv] failed to initialize\n"); + cControl::Shutdown(); + } + + running = 1; + + isyslog("[mpv] playing %s\n", PlayFilename.c_str()); + if (!IsPlaylist(PlayFilename)) + { + const char *cmd[] = {"loadfile", PlayFilename.c_str(), NULL}; + mpv_command(hMpv, cmd); + } + else + { + const char *cmd[] = {"loadlist", PlayFilename.c_str(), NULL}; + mpv_command(hMpv, cmd); + } + + // start thread to observe and react on mpv events + pthread_create(&ObserverThreadHandle, NULL, ObserverThread, this); + + // set back locale + setlocale(LC_NUMERIC, LocaleSave.c_str()); +} + +void cMpvPlayer::HandlePropertyChange(mpv_event *event) +{ + mpv_event_property *property = (mpv_event_property *) event->data; + + if (!property->data) + return; + + // don't log on time-pos change since this floods the log + if (event->reply_userdata != MPV_OBSERVE_TIME_POS) + { + dsyslog("[mpv]: property %s \n", property->name); + } + + switch (event->reply_userdata) + { + case MPV_OBSERVE_TIME_POS : + PlayerCurrent = (int)*(double*)property->data; + break; + + case MPV_OBSERVE_DISC_MENU : + PlayerDiscNav = (int)*(int64_t*)property->data; + break; + + case MPV_OBSERVE_FPS : + PlayerFps = (int)*(double*)property->data; + break; + + case MPV_OBSERVE_FILENAME : + PlayerFilename = *(char**)property->data; + break; + + case MPV_OBSERVE_LENGTH : + PlayerTotal = (int)*(double*)property->data; + break; + + case MPV_OBSERVE_CHAPTERS : + PlayerNumChapters = (int)*(int64_t*)property->data; + mpv_node Node; + mpv_get_property(hMpv, "chapter-list", MPV_FORMAT_NODE, &Node); + ChapterTitles.clear(); + PlayerChapters.clear(); + if (Node.format == MPV_FORMAT_NODE_ARRAY) + { + for (int i=0; i<Node.u.list->num; i++) + { + ChapterTitles.push_back (Node.u.list->values[i].u.list->values[0].u.string); + PlayerChapters.push_back (Node.u.list->values[i].u.list->values[1].u.double_); + } + } + break; + + case MPV_OBSERVE_CHAPTER : + PlayerChapter = (int)*(int64_t*)property->data; + break; + + case MPV_OBSERVE_PAUSE : + PlayerPaused = (int)*(int64_t*)property->data; + break; + + case MPV_OBSERVE_SPEED : + PlayerSpeed = (int)*(double*)property->data; + break; + + case MPV_OBSERVE_MEDIA_TITLE : + mediaTitle = *(char**)property->data; + break; + } +} + +void cMpvPlayer::HandleTracksChange() +{ + mpv_node Node; + mpv_get_property(hMpv, "track-list", MPV_FORMAT_NODE, &Node); + if (!Node.format == MPV_FORMAT_NODE_ARRAY) + return; + + // loop though available tracks + for (int i=0; i<Node.u.list->num; i++) + { + int TrackId; + string TrackType; + string TrackLanguage = "undefined"; + string TrackTitle = ""; + for (int j=0; j<Node.u.list->values[i].u.list->num; j++) + { + if (strcmp(Node.u.list->values[i].u.list->keys[j], "id") == 0) + TrackId = Node.u.list->values[i].u.list->values[j].u.int64; + if (strcmp(Node.u.list->values[i].u.list->keys[j], "type") == 0) + TrackType = Node.u.list->values[i].u.list->values[j].u.string; + if (strcmp(Node.u.list->values[i].u.list->keys[j], "lang") == 0) + TrackLanguage = Node.u.list->values[i].u.list->values[j].u.string; + if (strcmp(Node.u.list->values[i].u.list->keys[j], "title") == 0) + TrackTitle = Node.u.list->values[i].u.list->values[j].u.string; + } + if (TrackType == "audio") + { + eTrackType type = ttAudio; + DeviceSetAvailableTrack(type, i, TrackId, TrackLanguage.c_str(), TrackTitle.c_str()); + } + else if (TrackType == "sub") + { + eTrackType type = ttSubtitle; + DeviceSetAvailableTrack(type, 0, 0, "Off"); + DeviceSetAvailableTrack(type, i, TrackId, TrackLanguage.c_str(), TrackTitle.c_str()); + } + } +} + +void cMpvPlayer::OsdClose() +{ + dsyslog("[mpv] %s\n", __FUNCTION__); + + SendCommand ("overlay_remove 1"); +} + +void cMpvPlayer::Shutdown() +{ + mediaTitle = ""; + running = 0; + MpvPluginConfig->TitleOverride = ""; + ChapterTitles.clear(); + PlayerChapters.clear(); + + if (ObserverThreadHandle) + pthread_cancel(ObserverThreadHandle); + + mpv_terminate_destroy(hMpv); + hMpv = NULL; + cOsdProvider::Shutdown(); + if (MpvPluginConfig->RefreshRate) + { + ChangeFrameRate(OriginalFps); + OriginalFps = -1; + } +} + +void cMpvPlayer::SwitchOsdToMpv() +{ + dsyslog("[mpv] %s\n", __FUNCTION__); + cOsdProvider::Shutdown(); + new cMpvOsdProvider(this); +} + +bool cMpvPlayer::IsPlaylist(string File) +{ + for (unsigned int i=0;i<MpvPluginConfig->PlaylistExtensions.size();i++) + { + if (File.substr(File.find_last_of(".") + 1) == MpvPluginConfig->PlaylistExtensions[i]) + return true; + } + + return false; +} + +void cMpvPlayer::ChangeFrameRate(int TargetRate) +{ + #ifdef USE_XRANDR + if (!MpvPluginConfig->RefreshRate) + return; + + Display *Dpy; + int RefreshRate; + XRRScreenConfiguration *CurrInfo; + + if (TargetRate == 25) + TargetRate = 50; // fix DVD audio and since this is doubled it's ok + + if (TargetRate == 23) + TargetRate = 24; + + Dpy = XOpenDisplay(MpvPluginConfig->X11Display.c_str()); + + if (Dpy) + { + short *Rates; + int NumberOfRates; + SizeID CurrentSizeId; + Rotation CurrentRotation; + int RateFound = 0; + + CurrInfo = XRRGetScreenInfo(Dpy, DefaultRootWindow(Dpy)); + RefreshRate = XRRConfigCurrentRate(CurrInfo); + CurrentSizeId = + XRRConfigCurrentConfiguration(CurrInfo, &CurrentRotation); + Rates = XRRConfigRates(CurrInfo, CurrentSizeId, &NumberOfRates); + + while (NumberOfRates-- > 0) + { + if (TargetRate == *Rates++) + RateFound = 1; + } + + if ((RefreshRate != TargetRate) && (RateFound == 1)) + { + OriginalFps = RefreshRate; + XRRSetScreenConfigAndRate(Dpy, CurrInfo, DefaultRootWindow(Dpy), + CurrentSizeId, CurrentRotation, TargetRate, CurrentTime); + } + + XRRFreeScreenConfigInfo(CurrInfo); + XCloseDisplay(Dpy); + } +#endif +} + +void cMpvPlayer::SendCommand(const char *cmd, ...) +{ + if (!PlayerIsRunning()) + return; + va_list va; + char buf[256]; + va_start(va, cmd); + vsnprintf(buf, sizeof(buf), cmd, va); + va_end(va); + mpv_command_string(hMpv, buf); +} + +void cMpvPlayer::Seek(int Seconds) +{ + SendCommand("seek %d\n", Seconds); +} + +void cMpvPlayer::SetTimePos(int Seconds) +{ + SendCommand("set time-pos %d\n", Seconds); +} + +void cMpvPlayer::SetSpeed(int Speed) +{ + SendCommand("set speed %d\n", Speed); +} + +void cMpvPlayer::SetAudio(int Audio) +{ + SendCommand("set audio %d\n", Audio); +} + +void cMpvPlayer::SetSubtitle(int Subtitle) +{ + SendCommand("set sub %d\n", Subtitle); +} + +void cMpvPlayer::TogglePause() +{ + SendCommand("cycle pause\n"); +} + +void cMpvPlayer::QuitPlayer() +{ + SendCommand("quit\n"); +} + +void cMpvPlayer::DiscNavUp() +{ + SendCommand("discnav up\n"); +} + +void cMpvPlayer::DiscNavDown() +{ + SendCommand("discnav down\n"); +} + +void cMpvPlayer::DiscNavLeft() +{ + SendCommand("discnav left\n"); +} + +void cMpvPlayer::DiscNavRight() +{ + SendCommand("discnav right\n"); +} + +void cMpvPlayer::DiscNavMenu() +{ + SendCommand("discnav menu\n"); +} + +void cMpvPlayer::DiscNavSelect() +{ + SendCommand("discnav select\n"); +} + +void cMpvPlayer::DiscNavPrev() +{ + SendCommand("discnav prev\n"); +} + +void cMpvPlayer::PreviousChapter() +{ + SendCommand("cycle chapter -1\n"); +} + +void cMpvPlayer::NextChapter() +{ + SendCommand("cycle chapter 1\n"); +} + +void cMpvPlayer::NextPlaylistItem() +{ + SendCommand("pt_step 1\n"); +} + +void cMpvPlayer::PreviousPlaylistItem() +{ + SendCommand("pt_step -1\n"); +} + +void cMpvPlayer::SetVolume(int Volume) +{ + SendCommand("set volume %d\n", Volume); +} + |