1 /*
2 * Copyright (c) 2015 John R. Bradley <jrb@turrettech.com>
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16
17 #include <obs-module.h>
18 #include <util/platform.h>
19 #include <util/dstr.h>
20
21 #include "obs-ffmpeg-compat.h"
22 #include "obs-ffmpeg-formats.h"
23
24 #include <media-playback/media.h>
25
26 #define FF_LOG(level, format, ...) \
27 blog(level, "[Media Source]: " format, ##__VA_ARGS__)
28 #define FF_LOG_S(source, level, format, ...) \
29 blog(level, "[Media Source '%s']: " format, \
30 obs_source_get_name(source), ##__VA_ARGS__)
31 #define FF_BLOG(level, format, ...) \
32 FF_LOG_S(s->source, level, format, ##__VA_ARGS__)
33
34 struct ffmpeg_source {
35 mp_media_t media;
36 bool media_valid;
37 bool destroy_media;
38
39 struct SwsContext *sws_ctx;
40 int sws_width;
41 int sws_height;
42 enum AVPixelFormat sws_format;
43 uint8_t *sws_data;
44 int sws_linesize;
45 enum video_range_type range;
46 bool is_linear_alpha;
47 obs_source_t *source;
48 obs_hotkey_id hotkey;
49
50 char *input;
51 char *input_format;
52 int buffering_mb;
53 int speed_percent;
54 bool is_looping;
55 bool is_local_file;
56 bool is_hw_decoding;
57 bool is_clear_on_media_end;
58 bool restart_on_activate;
59 bool close_when_inactive;
60 bool seekable;
61
62 pthread_t reconnect_thread;
63 bool stop_reconnect;
64 bool reconnect_thread_valid;
65 volatile bool reconnecting;
66 int reconnect_delay_sec;
67
68 enum obs_media_state state;
69 obs_hotkey_pair_id play_pause_hotkey;
70 obs_hotkey_id stop_hotkey;
71 };
72
set_media_state(void * data,enum obs_media_state state)73 static void set_media_state(void *data, enum obs_media_state state)
74 {
75 struct ffmpeg_source *s = data;
76 s->state = state;
77 }
78
is_local_file_modified(obs_properties_t * props,obs_property_t * prop,obs_data_t * settings)79 static bool is_local_file_modified(obs_properties_t *props,
80 obs_property_t *prop, obs_data_t *settings)
81 {
82 UNUSED_PARAMETER(prop);
83
84 bool enabled = obs_data_get_bool(settings, "is_local_file");
85 obs_property_t *input = obs_properties_get(props, "input");
86 obs_property_t *input_format =
87 obs_properties_get(props, "input_format");
88 obs_property_t *local_file = obs_properties_get(props, "local_file");
89 obs_property_t *looping = obs_properties_get(props, "looping");
90 obs_property_t *buffering = obs_properties_get(props, "buffering_mb");
91 obs_property_t *seekable = obs_properties_get(props, "seekable");
92 obs_property_t *speed = obs_properties_get(props, "speed_percent");
93 obs_property_t *reconnect_delay_sec =
94 obs_properties_get(props, "reconnect_delay_sec");
95 obs_property_set_visible(input, !enabled);
96 obs_property_set_visible(input_format, !enabled);
97 obs_property_set_visible(buffering, !enabled);
98 obs_property_set_visible(local_file, enabled);
99 obs_property_set_visible(looping, enabled);
100 obs_property_set_visible(speed, enabled);
101 obs_property_set_visible(seekable, !enabled);
102 obs_property_set_visible(reconnect_delay_sec, !enabled);
103
104 return true;
105 }
106
ffmpeg_source_defaults(obs_data_t * settings)107 static void ffmpeg_source_defaults(obs_data_t *settings)
108 {
109 obs_data_set_default_bool(settings, "is_local_file", true);
110 obs_data_set_default_bool(settings, "looping", false);
111 obs_data_set_default_bool(settings, "clear_on_media_end", true);
112 obs_data_set_default_bool(settings, "restart_on_activate", true);
113 obs_data_set_default_bool(settings, "linear_alpha", false);
114 obs_data_set_default_int(settings, "reconnect_delay_sec", 10);
115 obs_data_set_default_int(settings, "buffering_mb", 2);
116 obs_data_set_default_int(settings, "speed_percent", 100);
117 }
118
119 static const char *media_filter =
120 " (*.mp4 *.ts *.mov *.flv *.mkv *.avi *.mp3 *.ogg *.aac *.wav *.gif *.webm);;";
121 static const char *video_filter =
122 " (*.mp4 *.ts *.mov *.flv *.mkv *.avi *.gif *.webm);;";
123 static const char *audio_filter = " (*.mp3 *.aac *.ogg *.wav);;";
124
ffmpeg_source_getproperties(void * data)125 static obs_properties_t *ffmpeg_source_getproperties(void *data)
126 {
127 struct ffmpeg_source *s = data;
128 struct dstr filter = {0};
129 struct dstr path = {0};
130 UNUSED_PARAMETER(data);
131
132 obs_properties_t *props = obs_properties_create();
133
134 obs_properties_set_flags(props, OBS_PROPERTIES_DEFER_UPDATE);
135
136 obs_property_t *prop;
137 // use this when obs allows non-readonly paths
138 prop = obs_properties_add_bool(props, "is_local_file",
139 obs_module_text("LocalFile"));
140
141 obs_property_set_modified_callback(prop, is_local_file_modified);
142
143 dstr_copy(&filter, obs_module_text("MediaFileFilter.AllMediaFiles"));
144 dstr_cat(&filter, media_filter);
145 dstr_cat(&filter, obs_module_text("MediaFileFilter.VideoFiles"));
146 dstr_cat(&filter, video_filter);
147 dstr_cat(&filter, obs_module_text("MediaFileFilter.AudioFiles"));
148 dstr_cat(&filter, audio_filter);
149 dstr_cat(&filter, obs_module_text("MediaFileFilter.AllFiles"));
150 dstr_cat(&filter, " (*.*)");
151
152 if (s && s->input && *s->input) {
153 const char *slash;
154
155 dstr_copy(&path, s->input);
156 dstr_replace(&path, "\\", "/");
157 slash = strrchr(path.array, '/');
158 if (slash)
159 dstr_resize(&path, slash - path.array + 1);
160 }
161
162 obs_properties_add_path(props, "local_file",
163 obs_module_text("LocalFile"), OBS_PATH_FILE,
164 filter.array, path.array);
165 dstr_free(&filter);
166 dstr_free(&path);
167
168 obs_properties_add_bool(props, "looping", obs_module_text("Looping"));
169
170 obs_properties_add_bool(props, "restart_on_activate",
171 obs_module_text("RestartWhenActivated"));
172
173 prop = obs_properties_add_int_slider(props, "buffering_mb",
174 obs_module_text("BufferingMB"), 0,
175 16, 1);
176 obs_property_int_set_suffix(prop, " MB");
177
178 obs_properties_add_text(props, "input", obs_module_text("Input"),
179 OBS_TEXT_DEFAULT);
180
181 obs_properties_add_text(props, "input_format",
182 obs_module_text("InputFormat"),
183 OBS_TEXT_DEFAULT);
184
185 prop = obs_properties_add_int_slider(
186 props, "reconnect_delay_sec",
187 obs_module_text("ReconnectDelayTime"), 1, 60, 1);
188 obs_property_int_set_suffix(prop, " S");
189
190 obs_properties_add_bool(props, "hw_decode",
191 obs_module_text("HardwareDecode"));
192
193 obs_properties_add_bool(props, "clear_on_media_end",
194 obs_module_text("ClearOnMediaEnd"));
195
196 prop = obs_properties_add_bool(
197 props, "close_when_inactive",
198 obs_module_text("CloseFileWhenInactive"));
199
200 obs_property_set_long_description(
201 prop, obs_module_text("CloseFileWhenInactive.ToolTip"));
202
203 prop = obs_properties_add_int_slider(props, "speed_percent",
204 obs_module_text("SpeedPercentage"),
205 1, 200, 1);
206 obs_property_int_set_suffix(prop, "%");
207
208 prop = obs_properties_add_list(props, "color_range",
209 obs_module_text("ColorRange"),
210 OBS_COMBO_TYPE_LIST,
211 OBS_COMBO_FORMAT_INT);
212 obs_property_list_add_int(prop, obs_module_text("ColorRange.Auto"),
213 VIDEO_RANGE_DEFAULT);
214 obs_property_list_add_int(prop, obs_module_text("ColorRange.Partial"),
215 VIDEO_RANGE_PARTIAL);
216 obs_property_list_add_int(prop, obs_module_text("ColorRange.Full"),
217 VIDEO_RANGE_FULL);
218
219 obs_properties_add_bool(props, "linear_alpha",
220 obs_module_text("LinearAlpha"));
221
222 obs_properties_add_bool(props, "seekable", obs_module_text("Seekable"));
223
224 return props;
225 }
226
dump_source_info(struct ffmpeg_source * s,const char * input,const char * input_format)227 static void dump_source_info(struct ffmpeg_source *s, const char *input,
228 const char *input_format)
229 {
230 FF_BLOG(LOG_INFO,
231 "settings:\n"
232 "\tinput: %s\n"
233 "\tinput_format: %s\n"
234 "\tspeed: %d\n"
235 "\tis_looping: %s\n"
236 "\tis_linear_alpha: %s\n"
237 "\tis_hw_decoding: %s\n"
238 "\tis_clear_on_media_end: %s\n"
239 "\trestart_on_activate: %s\n"
240 "\tclose_when_inactive: %s",
241 input ? input : "(null)",
242 input_format ? input_format : "(null)", s->speed_percent,
243 s->is_looping ? "yes" : "no", s->is_linear_alpha ? "yes" : "no",
244 s->is_hw_decoding ? "yes" : "no",
245 s->is_clear_on_media_end ? "yes" : "no",
246 s->restart_on_activate ? "yes" : "no",
247 s->close_when_inactive ? "yes" : "no");
248 }
249
get_frame(void * opaque,struct obs_source_frame * f)250 static void get_frame(void *opaque, struct obs_source_frame *f)
251 {
252 struct ffmpeg_source *s = opaque;
253 obs_source_output_video(s->source, f);
254 }
255
preload_frame(void * opaque,struct obs_source_frame * f)256 static void preload_frame(void *opaque, struct obs_source_frame *f)
257 {
258 struct ffmpeg_source *s = opaque;
259 if (s->close_when_inactive)
260 return;
261
262 if (s->is_clear_on_media_end || s->is_looping)
263 obs_source_preload_video(s->source, f);
264
265 if (!s->is_local_file && os_atomic_set_bool(&s->reconnecting, false))
266 FF_BLOG(LOG_INFO, "Reconnected.");
267 }
268
seek_frame(void * opaque,struct obs_source_frame * f)269 static void seek_frame(void *opaque, struct obs_source_frame *f)
270 {
271 struct ffmpeg_source *s = opaque;
272 obs_source_set_video_frame(s->source, f);
273 }
274
get_audio(void * opaque,struct obs_source_audio * a)275 static void get_audio(void *opaque, struct obs_source_audio *a)
276 {
277 struct ffmpeg_source *s = opaque;
278 obs_source_output_audio(s->source, a);
279
280 if (!s->is_local_file && os_atomic_set_bool(&s->reconnecting, false))
281 FF_BLOG(LOG_INFO, "Reconnected.");
282 }
283
media_stopped(void * opaque)284 static void media_stopped(void *opaque)
285 {
286 struct ffmpeg_source *s = opaque;
287 if (s->is_clear_on_media_end) {
288 obs_source_output_video(s->source, NULL);
289 }
290
291 if ((s->close_when_inactive || !s->is_local_file) && s->media_valid)
292 s->destroy_media = true;
293
294 set_media_state(s, OBS_MEDIA_STATE_ENDED);
295 obs_source_media_ended(s->source);
296 }
297
ffmpeg_source_open(struct ffmpeg_source * s)298 static void ffmpeg_source_open(struct ffmpeg_source *s)
299 {
300 if (s->input && *s->input) {
301 struct mp_media_info info = {
302 .opaque = s,
303 .v_cb = get_frame,
304 .v_preload_cb = preload_frame,
305 .v_seek_cb = seek_frame,
306 .a_cb = get_audio,
307 .stop_cb = media_stopped,
308 .path = s->input,
309 .format = s->input_format,
310 .buffering = s->buffering_mb * 1024 * 1024,
311 .speed = s->speed_percent,
312 .force_range = s->range,
313 .is_linear_alpha = s->is_linear_alpha,
314 .hardware_decoding = s->is_hw_decoding,
315 .is_local_file = s->is_local_file || s->seekable,
316 .reconnecting = s->reconnecting,
317 };
318
319 s->media_valid = mp_media_init(&s->media, &info);
320 }
321 }
322
ffmpeg_source_start(struct ffmpeg_source * s)323 static void ffmpeg_source_start(struct ffmpeg_source *s)
324 {
325 if (!s->media_valid)
326 ffmpeg_source_open(s);
327
328 if (!s->media_valid)
329 return;
330
331 mp_media_play(&s->media, s->is_looping, s->reconnecting);
332 if (s->is_local_file && (s->is_clear_on_media_end || s->is_looping))
333 obs_source_show_preloaded_video(s->source);
334 else
335 obs_source_output_video(s->source, NULL);
336 set_media_state(s, OBS_MEDIA_STATE_PLAYING);
337 obs_source_media_started(s->source);
338 }
339
ffmpeg_source_reconnect(void * data)340 static void *ffmpeg_source_reconnect(void *data)
341 {
342 struct ffmpeg_source *s = data;
343 os_sleep_ms(s->reconnect_delay_sec * 1000);
344
345 if (s->stop_reconnect || s->media_valid)
346 goto finish;
347
348 bool active = obs_source_active(s->source);
349 if (!s->close_when_inactive || active)
350 ffmpeg_source_open(s);
351
352 if (!s->restart_on_activate || active)
353 ffmpeg_source_start(s);
354
355 finish:
356 s->reconnect_thread_valid = false;
357 return NULL;
358 }
359
ffmpeg_source_tick(void * data,float seconds)360 static void ffmpeg_source_tick(void *data, float seconds)
361 {
362 UNUSED_PARAMETER(seconds);
363
364 struct ffmpeg_source *s = data;
365 if (s->destroy_media) {
366 if (s->media_valid) {
367 mp_media_free(&s->media);
368 s->media_valid = false;
369 }
370
371 s->destroy_media = false;
372
373 if (!s->is_local_file) {
374 if (!os_atomic_set_bool(&s->reconnecting, true)) {
375 FF_BLOG(LOG_WARNING, "Disconnected. "
376 "Reconnecting...");
377 }
378 if (pthread_create(&s->reconnect_thread, NULL,
379 ffmpeg_source_reconnect, s) != 0) {
380 FF_BLOG(LOG_WARNING, "Could not create "
381 "reconnect thread");
382 return;
383 }
384 s->reconnect_thread_valid = true;
385 }
386 }
387 }
388
ffmpeg_source_update(void * data,obs_data_t * settings)389 static void ffmpeg_source_update(void *data, obs_data_t *settings)
390 {
391 struct ffmpeg_source *s = data;
392
393 bool is_local_file = obs_data_get_bool(settings, "is_local_file");
394
395 char *input;
396 char *input_format;
397
398 bfree(s->input);
399 bfree(s->input_format);
400
401 if (is_local_file) {
402 input = (char *)obs_data_get_string(settings, "local_file");
403 input_format = NULL;
404 s->is_looping = obs_data_get_bool(settings, "looping");
405 } else {
406 input = (char *)obs_data_get_string(settings, "input");
407 input_format =
408 (char *)obs_data_get_string(settings, "input_format");
409 s->reconnect_delay_sec =
410 (int)obs_data_get_int(settings, "reconnect_delay_sec");
411 s->reconnect_delay_sec = s->reconnect_delay_sec == 0
412 ? 10
413 : s->reconnect_delay_sec;
414 s->is_looping = false;
415
416 if (s->reconnect_thread_valid) {
417 s->stop_reconnect = true;
418 pthread_join(s->reconnect_thread, NULL);
419 s->stop_reconnect = false;
420 }
421 }
422
423 s->close_when_inactive =
424 obs_data_get_bool(settings, "close_when_inactive");
425
426 s->input = input ? bstrdup(input) : NULL;
427 s->input_format = input_format ? bstrdup(input_format) : NULL;
428 s->is_hw_decoding = obs_data_get_bool(settings, "hw_decode");
429 s->is_clear_on_media_end =
430 obs_data_get_bool(settings, "clear_on_media_end");
431 s->restart_on_activate =
432 obs_data_get_bool(settings, "restart_on_activate");
433 s->range = (enum video_range_type)obs_data_get_int(settings,
434 "color_range");
435 s->is_linear_alpha = obs_data_get_bool(settings, "linear_alpha");
436 s->buffering_mb = (int)obs_data_get_int(settings, "buffering_mb");
437 s->speed_percent = (int)obs_data_get_int(settings, "speed_percent");
438 s->is_local_file = is_local_file;
439 s->seekable = obs_data_get_bool(settings, "seekable");
440
441 if (s->speed_percent < 1 || s->speed_percent > 200)
442 s->speed_percent = 100;
443
444 if (s->media_valid) {
445 mp_media_free(&s->media);
446 s->media_valid = false;
447 }
448
449 bool active = obs_source_active(s->source);
450 if (!s->close_when_inactive || active)
451 ffmpeg_source_open(s);
452
453 dump_source_info(s, input, input_format);
454 if (!s->restart_on_activate || active)
455 ffmpeg_source_start(s);
456 }
457
ffmpeg_source_getname(void * unused)458 static const char *ffmpeg_source_getname(void *unused)
459 {
460 UNUSED_PARAMETER(unused);
461 return obs_module_text("FFMpegSource");
462 }
463
restart_hotkey(void * data,obs_hotkey_id id,obs_hotkey_t * hotkey,bool pressed)464 static void restart_hotkey(void *data, obs_hotkey_id id, obs_hotkey_t *hotkey,
465 bool pressed)
466 {
467 UNUSED_PARAMETER(id);
468 UNUSED_PARAMETER(hotkey);
469
470 if (!pressed)
471 return;
472
473 struct ffmpeg_source *s = data;
474 if (obs_source_showing(s->source))
475 obs_source_media_restart(s->source);
476 }
477
restart_proc(void * data,calldata_t * cd)478 static void restart_proc(void *data, calldata_t *cd)
479 {
480 restart_hotkey(data, 0, NULL, true);
481 UNUSED_PARAMETER(cd);
482 }
483
get_duration(void * data,calldata_t * cd)484 static void get_duration(void *data, calldata_t *cd)
485 {
486 struct ffmpeg_source *s = data;
487 int64_t dur = 0;
488 if (s->media.fmt)
489 dur = s->media.fmt->duration;
490
491 calldata_set_int(cd, "duration", dur * 1000);
492 }
493
get_nb_frames(void * data,calldata_t * cd)494 static void get_nb_frames(void *data, calldata_t *cd)
495 {
496 struct ffmpeg_source *s = data;
497 int64_t frames = 0;
498
499 if (!s->media.fmt) {
500 calldata_set_int(cd, "num_frames", frames);
501 return;
502 }
503
504 int video_stream_index = av_find_best_stream(
505 s->media.fmt, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
506
507 if (video_stream_index < 0) {
508 FF_BLOG(LOG_WARNING, "Getting number of frames failed: No "
509 "video stream in media file!");
510 calldata_set_int(cd, "num_frames", frames);
511 return;
512 }
513
514 AVStream *stream = s->media.fmt->streams[video_stream_index];
515
516 if (stream->nb_frames > 0) {
517 frames = stream->nb_frames;
518 } else {
519 FF_BLOG(LOG_DEBUG, "nb_frames not set, estimating using frame "
520 "rate and duration");
521 AVRational avg_frame_rate = stream->avg_frame_rate;
522 frames = (int64_t)ceil((double)s->media.fmt->duration /
523 (double)AV_TIME_BASE *
524 (double)avg_frame_rate.num /
525 (double)avg_frame_rate.den);
526 }
527
528 calldata_set_int(cd, "num_frames", frames);
529 }
530
ffmpeg_source_play_hotkey(void * data,obs_hotkey_pair_id id,obs_hotkey_t * hotkey,bool pressed)531 static bool ffmpeg_source_play_hotkey(void *data, obs_hotkey_pair_id id,
532 obs_hotkey_t *hotkey, bool pressed)
533 {
534 UNUSED_PARAMETER(id);
535 UNUSED_PARAMETER(hotkey);
536
537 if (!pressed)
538 return false;
539
540 struct ffmpeg_source *s = data;
541
542 if (s->state == OBS_MEDIA_STATE_PLAYING ||
543 !obs_source_showing(s->source))
544 return false;
545
546 obs_source_media_play_pause(s->source, false);
547 return true;
548 }
549
ffmpeg_source_pause_hotkey(void * data,obs_hotkey_pair_id id,obs_hotkey_t * hotkey,bool pressed)550 static bool ffmpeg_source_pause_hotkey(void *data, obs_hotkey_pair_id id,
551 obs_hotkey_t *hotkey, bool pressed)
552 {
553 UNUSED_PARAMETER(id);
554 UNUSED_PARAMETER(hotkey);
555
556 if (!pressed)
557 return false;
558
559 struct ffmpeg_source *s = data;
560
561 if (s->state != OBS_MEDIA_STATE_PLAYING ||
562 !obs_source_showing(s->source))
563 return false;
564
565 obs_source_media_play_pause(s->source, true);
566 return true;
567 }
568
ffmpeg_source_stop_hotkey(void * data,obs_hotkey_id id,obs_hotkey_t * hotkey,bool pressed)569 static void ffmpeg_source_stop_hotkey(void *data, obs_hotkey_id id,
570 obs_hotkey_t *hotkey, bool pressed)
571 {
572 UNUSED_PARAMETER(id);
573 UNUSED_PARAMETER(hotkey);
574
575 if (!pressed)
576 return;
577
578 struct ffmpeg_source *s = data;
579
580 if (obs_source_showing(s->source))
581 obs_source_media_stop(s->source);
582 }
583
ffmpeg_source_create(obs_data_t * settings,obs_source_t * source)584 static void *ffmpeg_source_create(obs_data_t *settings, obs_source_t *source)
585 {
586 UNUSED_PARAMETER(settings);
587
588 struct ffmpeg_source *s = bzalloc(sizeof(struct ffmpeg_source));
589 s->source = source;
590
591 s->hotkey = obs_hotkey_register_source(source, "MediaSource.Restart",
592 obs_module_text("RestartMedia"),
593 restart_hotkey, s);
594
595 s->play_pause_hotkey = obs_hotkey_pair_register_source(
596 s->source, "MediaSource.Play", obs_module_text("Play"),
597 "MediaSource.Pause", obs_module_text("Pause"),
598 ffmpeg_source_play_hotkey, ffmpeg_source_pause_hotkey, s, s);
599
600 s->stop_hotkey = obs_hotkey_register_source(source, "MediaSource.Stop",
601 obs_module_text("Stop"),
602 ffmpeg_source_stop_hotkey,
603 s);
604
605 proc_handler_t *ph = obs_source_get_proc_handler(source);
606 proc_handler_add(ph, "void restart()", restart_proc, s);
607 proc_handler_add(ph, "void get_duration(out int duration)",
608 get_duration, s);
609 proc_handler_add(ph, "void get_nb_frames(out int num_frames)",
610 get_nb_frames, s);
611
612 ffmpeg_source_update(s, settings);
613 return s;
614 }
615
ffmpeg_source_destroy(void * data)616 static void ffmpeg_source_destroy(void *data)
617 {
618 struct ffmpeg_source *s = data;
619
620 if (s->hotkey)
621 obs_hotkey_unregister(s->hotkey);
622 if (!s->is_local_file) {
623 s->stop_reconnect = true;
624 if (s->reconnect_thread_valid)
625 pthread_join(s->reconnect_thread, NULL);
626 }
627 if (s->media_valid)
628 mp_media_free(&s->media);
629
630 if (s->sws_ctx != NULL)
631 sws_freeContext(s->sws_ctx);
632 bfree(s->sws_data);
633 bfree(s->input);
634 bfree(s->input_format);
635 bfree(s);
636 }
637
ffmpeg_source_activate(void * data)638 static void ffmpeg_source_activate(void *data)
639 {
640 struct ffmpeg_source *s = data;
641
642 if (s->restart_on_activate)
643 obs_source_media_restart(s->source);
644 }
645
ffmpeg_source_deactivate(void * data)646 static void ffmpeg_source_deactivate(void *data)
647 {
648 struct ffmpeg_source *s = data;
649
650 if (s->restart_on_activate) {
651 if (s->media_valid) {
652 mp_media_stop(&s->media);
653
654 if (s->is_clear_on_media_end)
655 obs_source_output_video(s->source, NULL);
656 }
657 }
658 }
659
ffmpeg_source_play_pause(void * data,bool pause)660 static void ffmpeg_source_play_pause(void *data, bool pause)
661 {
662 struct ffmpeg_source *s = data;
663
664 if (!s->media_valid)
665 ffmpeg_source_open(s);
666
667 if (!s->media_valid)
668 return;
669
670 mp_media_play_pause(&s->media, pause);
671
672 if (pause) {
673
674 set_media_state(s, OBS_MEDIA_STATE_PAUSED);
675 } else {
676
677 set_media_state(s, OBS_MEDIA_STATE_PLAYING);
678 obs_source_media_started(s->source);
679 }
680 }
681
ffmpeg_source_stop(void * data)682 static void ffmpeg_source_stop(void *data)
683 {
684 struct ffmpeg_source *s = data;
685
686 if (s->media_valid) {
687 mp_media_stop(&s->media);
688 obs_source_output_video(s->source, NULL);
689 set_media_state(s, OBS_MEDIA_STATE_STOPPED);
690 }
691 }
692
ffmpeg_source_restart(void * data)693 static void ffmpeg_source_restart(void *data)
694 {
695 struct ffmpeg_source *s = data;
696
697 if (obs_source_showing(s->source))
698 ffmpeg_source_start(s);
699
700 set_media_state(s, OBS_MEDIA_STATE_PLAYING);
701 }
702
ffmpeg_source_get_duration(void * data)703 static int64_t ffmpeg_source_get_duration(void *data)
704 {
705 struct ffmpeg_source *s = data;
706 int64_t dur = 0;
707
708 if (s->media.fmt)
709 dur = s->media.fmt->duration / INT64_C(1000);
710
711 return dur;
712 }
713
ffmpeg_source_get_time(void * data)714 static int64_t ffmpeg_source_get_time(void *data)
715 {
716 struct ffmpeg_source *s = data;
717
718 return mp_get_current_time(&s->media);
719 }
720
ffmpeg_source_set_time(void * data,int64_t ms)721 static void ffmpeg_source_set_time(void *data, int64_t ms)
722 {
723 struct ffmpeg_source *s = data;
724
725 if (!s->media_valid)
726 return;
727
728 mp_media_seek_to(&s->media, ms);
729 }
730
ffmpeg_source_get_state(void * data)731 static enum obs_media_state ffmpeg_source_get_state(void *data)
732 {
733 struct ffmpeg_source *s = data;
734
735 return s->state;
736 }
737
missing_file_callback(void * src,const char * new_path,void * data)738 static void missing_file_callback(void *src, const char *new_path, void *data)
739 {
740 struct ffmpeg_source *s = src;
741
742 obs_source_t *source = s->source;
743 obs_data_t *settings = obs_source_get_settings(source);
744 obs_data_set_string(settings, "local_file", new_path);
745 obs_source_update(source, settings);
746 obs_data_release(settings);
747
748 UNUSED_PARAMETER(data);
749 }
750
ffmpeg_source_missingfiles(void * data)751 static obs_missing_files_t *ffmpeg_source_missingfiles(void *data)
752 {
753 struct ffmpeg_source *s = data;
754 obs_missing_files_t *files = obs_missing_files_create();
755
756 if (s->is_local_file && strcmp(s->input, "") != 0) {
757 if (!os_file_exists(s->input)) {
758 obs_missing_file_t *file = obs_missing_file_create(
759 s->input, missing_file_callback,
760 OBS_MISSING_FILE_SOURCE, s->source, NULL);
761
762 obs_missing_files_add_file(files, file);
763 }
764 }
765
766 return files;
767 }
768
769 struct obs_source_info ffmpeg_source = {
770 .id = "ffmpeg_source",
771 .type = OBS_SOURCE_TYPE_INPUT,
772 .output_flags = OBS_SOURCE_ASYNC_VIDEO | OBS_SOURCE_AUDIO |
773 OBS_SOURCE_DO_NOT_DUPLICATE |
774 OBS_SOURCE_CONTROLLABLE_MEDIA,
775 .get_name = ffmpeg_source_getname,
776 .create = ffmpeg_source_create,
777 .destroy = ffmpeg_source_destroy,
778 .get_defaults = ffmpeg_source_defaults,
779 .get_properties = ffmpeg_source_getproperties,
780 .activate = ffmpeg_source_activate,
781 .deactivate = ffmpeg_source_deactivate,
782 .video_tick = ffmpeg_source_tick,
783 .missing_files = ffmpeg_source_missingfiles,
784 .update = ffmpeg_source_update,
785 .icon_type = OBS_ICON_TYPE_MEDIA,
786 .media_play_pause = ffmpeg_source_play_pause,
787 .media_restart = ffmpeg_source_restart,
788 .media_stop = ffmpeg_source_stop,
789 .media_get_duration = ffmpeg_source_get_duration,
790 .media_get_time = ffmpeg_source_get_time,
791 .media_set_time = ffmpeg_source_set_time,
792 .media_get_state = ffmpeg_source_get_state,
793 };
794