]> git.sur5r.net Git - minitube/blob - lib/media/src/mpv/mediampv.cpp
New upstream version 3.4.1
[minitube] / lib / media / src / mpv / mediampv.cpp
1 #include "mediampv.h"
2
3 #include "qthelper.hpp"
4 #include <clocale>
5
6 #ifndef MEDIA_AUDIOONLY
7 #include "mpvwidget.h"
8 #endif
9
10 namespace {
11 void wakeup(void *ctx) {
12     // This callback is invoked from any mpv thread (but possibly also
13     // recursively from a thread that is calling the mpv API). Just notify
14     // the Qt GUI thread to wake up (so that it can process events with
15     // mpv_wait_event()), and return as quickly as possible.
16     MediaMPV *mediaMPV = (MediaMPV *)ctx;
17     emit mediaMPV->mpvEvents();
18 }
19
20 } // namespace
21
22 MediaMPV::MediaMPV(QObject *parent) : Media(parent), widget(nullptr) {
23     QThread *thread = new QThread(this);
24     thread->start();
25     moveToThread(thread);
26     connect(this, &QObject::destroyed, thread, &QThread::quit);
27
28 #ifndef Q_OS_WIN
29     // Qt sets the locale in the QApplication constructor, but libmpv requires
30     // the LC_NUMERIC category to be set to "C", so change it back.
31     std::setlocale(LC_NUMERIC, "C");
32 #endif
33
34     mpv = mpv_create();
35     if (!mpv) qFatal("Cannot create MPV instance");
36
37     mpv_set_option_string(mpv, "config", "no");
38     mpv_set_option_string(mpv, "audio-display", "no");
39     mpv_set_option_string(mpv, "gapless-audio", "weak");
40     mpv_set_option_string(mpv, "idle", "yes");
41     mpv_set_option_string(mpv, "input-default-bindings", "no");
42     mpv_set_option_string(mpv, "input-vo-keyboard", "no");
43     mpv_set_option_string(mpv, "input-cursor", "no");
44     mpv_set_option_string(mpv, "input-media-keys", "no");
45     mpv_set_option_string(mpv, "ytdl", "no");
46     mpv_set_option_string(mpv, "fs", "no");
47     mpv_set_option_string(mpv, "osd-level", "0");
48     mpv_set_option_string(mpv, "quiet", "yes");
49     mpv_set_option_string(mpv, "load-scripts", "no");
50     mpv_set_option_string(mpv, "audio-client-name",
51                           QCoreApplication::applicationName().toUtf8().data());
52     mpv_set_option_string(mpv, "hwdec", "auto");
53     mpv_set_option_string(mpv, "vo", "libmpv");
54
55     mpv_set_option_string(mpv, "cache", "no");
56     mpv_set_option_string(mpv, "demuxer-max-bytes", "10485760");
57     mpv_set_option_string(mpv, "demuxer-max-back-bytes", "10485760");
58
59 #ifdef MEDIA_MPV_WID
60     widget = new QWidget();
61     widget->setAttribute(Qt::WA_DontCreateNativeAncestors);
62     widget->setAttribute(Qt::WA_NativeWindow);
63     // If you have a HWND, use: int64_t wid = (intptr_t)hwnd;
64     int64_t wid = (intptr_t)widget->winId();
65     mpv_set_option(mpv, "wid", MPV_FORMAT_INT64, &wid);
66 #endif
67
68 #ifndef QT_NO_DEBUG_OUTPUT
69     // Request log messages
70     // They are received as MPV_EVENT_LOG_MESSAGE.
71     mpv_request_log_messages(mpv, "info");
72 #endif
73
74     // From this point on, the wakeup function will be called. The callback
75     // can come from any thread, so we use the QueuedConnection mechanism to
76     // relay the wakeup in a thread-safe way.
77     connect(this, &MediaMPV::mpvEvents, this, &MediaMPV::onMpvEvents, Qt::QueuedConnection);
78     mpv_set_wakeup_callback(mpv, wakeup, this);
79
80     if (mpv_initialize(mpv) < 0) qFatal("mpv failed to initialize");
81
82     // Let us receive property change events with MPV_EVENT_PROPERTY_CHANGE if
83     // this property changes.
84     mpv_observe_property(mpv, 0, "time-pos", MPV_FORMAT_DOUBLE);
85     mpv_observe_property(mpv, 0, "duration", MPV_FORMAT_DOUBLE);
86     mpv_observe_property(mpv, 0, "volume", MPV_FORMAT_DOUBLE);
87     mpv_observe_property(mpv, 0, "mute", MPV_FORMAT_FLAG);
88     mpv_observe_property(mpv, 0, "pause", MPV_FORMAT_FLAG);
89 }
90
91 // This slot is invoked by wakeup() (through the mpvEvents signal).
92 void MediaMPV::onMpvEvents() {
93     // Process all events, until the event queue is empty.
94     while (mpv) {
95         mpv_event *event = mpv_wait_event(mpv, 0);
96         if (event->event_id == MPV_EVENT_NONE) break;
97         handleMpvEvent(event);
98     }
99 }
100
101 void MediaMPV::checkAboutToFinish(qint64 position) {
102     if (!aboutToFinishEmitted && currentState == Media::PlayingState) {
103         const qint64 dur = duration();
104         if (dur > 0 && dur - position < 5000) {
105             aboutToFinishEmitted = true;
106             qDebug() << "aboutToFinish" << position << dur;
107             emit aboutToFinish();
108         }
109     }
110 }
111
112 void MediaMPV::handleMpvEvent(mpv_event *event) {
113     // qDebug() << event->event_id << event->data;
114     switch (event->event_id) {
115     case MPV_EVENT_START_FILE:
116         clearTrackState();
117         emit sourceChanged();
118         setState(Media::LoadingState);
119         break;
120
121     case MPV_EVENT_SEEK:
122         setState(Media::BufferingState);
123         break;
124
125     case MPV_EVENT_PLAYBACK_RESTART: {
126         int pause;
127         mpv_get_property(mpv, "pause", MPV_FORMAT_FLAG, &pause);
128         bool paused = pause == 1;
129         if (paused)
130             setState(Media::PausedState);
131         else
132             setState(Media::PlayingState);
133         break;
134     }
135
136     case MPV_EVENT_FILE_LOADED:
137         setState(Media::PlayingState);
138         break;
139
140     case MPV_EVENT_END_FILE: {
141         struct mpv_event_end_file *eof_event = (struct mpv_event_end_file *)event->data;
142         if (eof_event->reason == MPV_END_FILE_REASON_EOF ||
143             eof_event->reason == MPV_END_FILE_REASON_ERROR) {
144             qDebug() << "Finished";
145             setState(Media::StoppedState);
146             emit finished();
147         }
148         break;
149     }
150
151     case MPV_EVENT_PROPERTY_CHANGE: {
152         mpv_event_property *prop = (mpv_event_property *)event->data;
153         qDebug() << prop->name << prop->data;
154
155         if (strcmp(prop->name, "time-pos") == 0) {
156             if (prop->format == MPV_FORMAT_DOUBLE) {
157                 double seconds = *(double *)prop->data;
158                 qint64 ms = seconds * 1000.;
159                 emit positionChanged(ms);
160                 checkAboutToFinish(ms);
161             }
162         }
163
164         else if (strcmp(prop->name, "volume") == 0) {
165             if (prop->format == MPV_FORMAT_DOUBLE) {
166                 double vol = *(double *)prop->data;
167                 emit volumeChanged(vol / 100.);
168             }
169         }
170
171         else if (strcmp(prop->name, "mute") == 0) {
172             if (prop->format == MPV_FORMAT_FLAG) {
173                 int mute = *(int *)prop->data;
174                 emit volumeMutedChanged(mute == 1);
175             }
176         }
177
178         else if (strcmp(prop->name, "pause") == 0) {
179             if (prop->format == MPV_FORMAT_FLAG) {
180                 int pause = *(int *)prop->data;
181                 bool paused = pause == 1;
182                 if (paused)
183                     setState(Media::PausedState);
184                 else {
185                     int coreIdle;
186                     mpv_get_property(mpv, "core-idle", MPV_FORMAT_FLAG, &coreIdle);
187                     if (coreIdle == 1)
188                         setState(Media::StoppedState);
189                     else
190                         setState(Media::PlayingState);
191                 }
192             }
193         }
194
195         break;
196     }
197
198     case MPV_EVENT_LOG_MESSAGE: {
199         struct mpv_event_log_message *msg = (struct mpv_event_log_message *)event->data;
200         qDebug() << "[" << msg->prefix << "] " << msg->level << ": " << msg->text;
201
202         if (msg->log_level == MPV_LOG_LEVEL_ERROR) {
203             lastErrorString = QString::fromUtf8(msg->text);
204             emit error(lastErrorString);
205         }
206
207         break;
208     }
209
210     case MPV_EVENT_SHUTDOWN: {
211         mpv_terminate_destroy(mpv);
212         mpv = nullptr;
213         break;
214     }
215
216     default:;
217         // Unhandled events
218     }
219 }
220
221 void MediaMPV::sendCommand(const char *args[]) {
222     // mpv_command_async(mpv, 0, args);
223     mpv_command(mpv, args);
224 }
225
226 void MediaMPV::setState(Media::State value) {
227     if (value != currentState) {
228         qDebug() << "State" << value;
229         currentState = value;
230         emit stateChanged(currentState);
231     }
232 }
233
234 void MediaMPV::clearTrackState() {
235     lastErrorString.clear();
236     aboutToFinishEmitted = false;
237 }
238
239 void MediaMPV::setAudioOnly(bool value) {
240     Q_UNUSED(value);
241 }
242
243 #ifndef MEDIA_AUDIOONLY
244
245 void MediaMPV::setRenderer(const QString &name) {
246     mpv_set_option_string(mpv, "vo", name.toUtf8().data());
247 }
248
249 QWidget *MediaMPV::videoWidget() {
250     if (!widget) {
251         widget = new MpvWidget(mpv);
252     }
253     return widget;
254 }
255
256 void MediaMPV::playSeparateAudioAndVideo(const QString &video, const QString &audio) {
257     const QByteArray fileUtf8 = video.toUtf8();
258     const char *args[] = {"loadfile", fileUtf8.constData(), nullptr};
259     sendCommand(args);
260
261     qApp->processEvents();
262
263     const QByteArray audioUtf8 = audio.toUtf8();
264     const char *args2[] = {"audio-add", audioUtf8.constData(), nullptr};
265     sendCommand(args2);
266
267     clearTrackState();
268 }
269
270 void MediaMPV::snapshot() {
271     if (currentState == State::StoppedState) return;
272
273     const QVariantList args = {"screenshot-raw", "video"};
274     mpv::qt::node_builder nodeBuilder(args);
275     mpv_node node;
276     const int ret = mpv_command_node(mpv, nodeBuilder.node(), &node);
277     if (ret < 0) {
278         emit error("Cannot take snapshot");
279         return;
280     }
281
282     mpv::qt::node_autofree auto_free(&node);
283     if (node.format != MPV_FORMAT_NODE_MAP) {
284         emit error("Cannot take snapshot");
285         return;
286     }
287
288     int width = 0;
289     int height = 0;
290     int stride = 0;
291     mpv_node_list *list = node.u.list;
292     uchar *data = nullptr;
293
294     for (int i = 0; i < list->num; ++i) {
295         const char *key = list->keys[i];
296         if (strcmp(key, "w") == 0) {
297             width = static_cast<int>(list->values[i].u.int64);
298         } else if (strcmp(key, "h") == 0) {
299             height = static_cast<int>(list->values[i].u.int64);
300         } else if (strcmp(key, "stride") == 0) {
301             stride = static_cast<int>(list->values[i].u.int64);
302         } else if (strcmp(key, "data") == 0) {
303             data = static_cast<uchar *>(list->values[i].u.ba->data);
304         }
305     }
306
307     if (data != nullptr) {
308         QImage img = QImage(data, width, height, stride, QImage::Format_RGB32);
309         img.bits();
310         emit snapshotReady(img);
311     }
312 }
313
314 #endif
315
316 void MediaMPV::init() {}
317
318 Media::State MediaMPV::state() const {
319     return currentState;
320 }
321
322 void MediaMPV::play(const QString &file) {
323     const QByteArray fileUtf8 = file.toUtf8();
324     const char *args[] = {"loadfile", fileUtf8.constData(), nullptr};
325     sendCommand(args);
326
327     clearTrackState();
328     if (currentState == Media::PausedState) play();
329 }
330
331 void MediaMPV::play() {
332     int flag = 0;
333     mpv_set_property_async(mpv, 0, "pause", MPV_FORMAT_FLAG, &flag);
334 }
335
336 void MediaMPV::pause() {
337     int flag = 1;
338     mpv_set_property_async(mpv, 0, "pause", MPV_FORMAT_FLAG, &flag);
339 }
340
341 void MediaMPV::stop() {
342     const char *args[] = {"stop", nullptr};
343     sendCommand(args);
344 }
345
346 void MediaMPV::seek(qint64 ms) {
347     double seconds = ms / 1000.;
348     QByteArray ba = QString::number(seconds).toUtf8();
349     const char *args[] = {"seek", ba.constData(), "absolute", nullptr};
350     sendCommand(args);
351 }
352
353 QString MediaMPV::file() const {
354     char *path;
355     mpv_get_property(mpv, "path", MPV_FORMAT_STRING, &path);
356     return QString::fromUtf8(path);
357 }
358
359 void MediaMPV::setBufferMilliseconds(qint64 value) {
360     Q_UNUSED(value);
361     // Not implemented
362 }
363
364 void MediaMPV::setUserAgent(const QString &value) {
365     mpv_set_option_string(mpv, "user-agent", value.toUtf8());
366 }
367
368 void MediaMPV::enqueue(const QString &file) {
369     const QByteArray fileUtf8 = file.toUtf8();
370     const char *args[] = {"loadfile", fileUtf8.constData(), "append", nullptr};
371     sendCommand(args);
372 }
373
374 void MediaMPV::clearQueue() {
375     const char *args[] = {"playlist-clear", nullptr};
376     sendCommand(args);
377 }
378
379 bool MediaMPV::hasQueue() const {
380     mpv_node node;
381     int r = mpv_get_property(mpv, "playlist", MPV_FORMAT_NODE, &node);
382     if (r < 0) return false;
383     QVariant v = mpv::qt::node_to_variant(&node);
384     mpv_free_node_contents(&node);
385     QVariantList list = v.toList();
386     return list.count() > 1;
387 }
388
389 qint64 MediaMPV::position() const {
390     double seconds;
391     mpv_get_property(mpv, "time-pos", MPV_FORMAT_DOUBLE, &seconds);
392     return seconds * 1000.;
393 }
394
395 qint64 MediaMPV::duration() const {
396     double seconds;
397     mpv_get_property(mpv, "duration", MPV_FORMAT_DOUBLE, &seconds);
398     return seconds * 1000.;
399 }
400
401 qint64 MediaMPV::remainingTime() const {
402     double seconds;
403     mpv_get_property(mpv, "time-remaining", MPV_FORMAT_DOUBLE, &seconds);
404     return seconds * 1000.;
405 }
406
407 qreal MediaMPV::volume() const {
408     double vol;
409     mpv_get_property(mpv, "volume", MPV_FORMAT_DOUBLE, &vol);
410     return vol / 100.;
411 }
412
413 void MediaMPV::setVolume(qreal value) {
414     double percent = value * 100.;
415     mpv_set_property_async(mpv, 0, "volume", MPV_FORMAT_DOUBLE, &percent);
416 }
417
418 bool MediaMPV::volumeMuted() const {
419     int mute;
420     mpv_get_property(mpv, "mute", MPV_FORMAT_FLAG, &mute);
421     return mute == 1;
422 }
423
424 void MediaMPV::setVolumeMuted(bool value) {
425     int mute = value ? 1 : 0;
426     mpv_set_property(mpv, "mute", MPV_FORMAT_FLAG, &mute);
427 }
428
429 QString MediaMPV::errorString() const {
430     return lastErrorString;
431 }