/*****************************************************************************
* player.c: libvlc media player API sample usage
*****************************************************************************
* Copyright (C) 2024 VLC authors and VideoLAN
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <locale.h>
#include <semaphore.h>
#include <errno.h>
#include <inttypes.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/select.h>
#include <time.h>
#ifdef __linux__
#include <termios.h>
#include <fcntl.h>
#endif
#include <vlc/vlc.h>
struct context
{
libvlc_media_list_t *mlist;
libvlc_media_player_t *mp;
int playing_idx;
char *playing_title;
int pipefds[2];
pthread_mutex_t mainloop_lock;
libvlc_media_player_time_point_t time_point;
libvlc_time_t paused_date;
bool time_seeking;
float volume;
};
static void
print_usage(const char *name)
{
fprintf(stderr, "Usage: %s <media1> [media2]...\n", name);
}
static void
print_ui(struct context *ctx)
{
printf("\n"
"| seek/volume: arrows - previous/next: p/n - pause: space - next_frame: e - quit: q\n"
"| playing: \"%s\"\n",
ctx->playing_title);
}
static libvlc_media_t *
get_next_media_locked(struct context *ctx)
{
int next_index = ctx->playing_idx + 1;
return libvlc_media_list_count(ctx->mlist) > next_index ?
libvlc_media_list_item_at_index(ctx->mlist, next_index) : NULL;
}
static void
player_on_media_changed(void *opaque, libvlc_media_t *media)
{
struct context *ctx = opaque;
if (media == NULL)
return;
free(ctx->playing_title);
ctx->playing_title = libvlc_media_get_meta(media, libvlc_meta_Title);
print_ui(ctx);
/* Fetch the next media from the mlist */
libvlc_media_list_lock(ctx->mlist);
ctx->playing_idx = libvlc_media_list_index_of_item(ctx->mlist, media);
assert(ctx->playing_idx >= 0);
libvlc_media_t *next_media = get_next_media_locked(ctx);
libvlc_media_list_unlock(ctx->mlist);
/* Set the next media */
if (next_media != NULL)
{
libvlc_media_player_set_next_media(ctx->mp, next_media);
libvlc_media_release(next_media);
}
}
static void
player_on_media_subitems_changed(void *opaque, libvlc_media_t *media)
{
struct context *ctx = opaque;
assert(ctx->playing_idx == libvlc_media_list_index_of_item(ctx->mlist, media));
libvlc_media_list_t *subitems = libvlc_media_subitems(media);
if (subitems == NULL)
return;
libvlc_media_type_t type = libvlc_media_get_type(media);
libvlc_media_list_lock(ctx->mlist);
/* Remove the current media if it contain only subitems as the list will be
* flattened */
if (type == libvlc_media_type_directory || type == libvlc_media_type_playlist)
{
libvlc_media_list_remove_index(ctx->mlist, ctx->playing_idx);
ctx->playing_idx--;
}
libvlc_media_list_lock(subitems);
/* Add subitems to the current flattened playlist */
int end_id = libvlc_media_list_count(subitems) + ctx->playing_idx + 1;
for (int i = ctx->playing_idx + 1, j = 0; i < end_id; ++i, ++j)
{
libvlc_media_t *subitem = libvlc_media_list_item_at_index(subitems, j);
libvlc_media_list_insert_media(ctx->mlist, subitem, i);
libvlc_media_release(subitem);
}
libvlc_media_t *next_media = get_next_media_locked(ctx);
libvlc_media_list_unlock(subitems);
libvlc_media_list_unlock(ctx->mlist);
libvlc_media_list_release(subitems);
/* Set the next media */
if (next_media != NULL)
{
libvlc_media_player_set_next_media(ctx->mp, next_media);
libvlc_media_release(next_media);
}
}
static void
player_on_audio_volume_changed(void *opaque, float volume)
{
struct context *ctx = opaque;
pthread_mutex_lock(&ctx->mainloop_lock);
ctx->volume = volume;
pthread_mutex_unlock(&ctx->mainloop_lock);
char buf = 'u';
write(ctx->pipefds[1], &buf, 1);
}
static void
player_time_on_update(void *opaque,
const libvlc_media_player_time_point_t *value)
{
struct context *ctx = opaque;
if (ctx->time_seeking)
return;
pthread_mutex_lock(&ctx->mainloop_lock);
ctx->paused_date = 0;
ctx->time_point = *value;
pthread_mutex_unlock(&ctx->mainloop_lock);
char buf = 'u';
write(ctx->pipefds[1], &buf, 1);
}
static void
player_time_on_paused(void *opaque, libvlc_time_t system_date_us)
{
struct context *ctx = opaque;
fprintf(stderr, "player_time_on_paused\n");
pthread_mutex_lock(&ctx->mainloop_lock);
ctx->paused_date = system_date_us;
pthread_mutex_unlock(&ctx->mainloop_lock);
char buf = 'u';
write(ctx->pipefds[1], &buf, 1);
}
static void
player_time_on_seek(void *opaque, const libvlc_media_player_time_point_t *value)
{
struct context *ctx = opaque;
pthread_mutex_lock(&ctx->mainloop_lock);
if (value != NULL)
{
ctx->time_point = *value;
ctx->time_seeking = true;
}
else
ctx->time_seeking = false;
pthread_mutex_unlock(&ctx->mainloop_lock);
char buf = 'u';
write(ctx->pipefds[1], &buf, 1);
}
static void
context_destroy(struct context *ctx)
{
libvlc_media_list_release(ctx->mlist);
libvlc_media_player_unwatch_time(ctx->mp);
libvlc_media_player_release(ctx->mp);
pthread_mutex_destroy(&ctx->mainloop_lock);
close(ctx->pipefds[0]);
close(ctx->pipefds[1]);
free(ctx->playing_title);
}
static int
context_init(struct context *ctx, libvlc_instance_t *libvlc)
{
ctx->playing_idx = 0;
ctx->playing_title = NULL;
ctx->paused_date = 0;
ctx->time_seeking = false;
ctx->volume = 1.f;
int ret = pipe(ctx->pipefds);
if (ret != 0)
return -errno;
pthread_mutex_init(&ctx->mainloop_lock, NULL);
ctx->mlist = libvlc_media_list_new();
if (ctx->mlist == NULL)
goto error_pipe;
static const struct libvlc_media_player_cbs cbs = {
.version = 0,
.on_media_changed = player_on_media_changed,
.on_media_subitems_changed = player_on_media_subitems_changed,
.on_audio_volume_changed = player_on_audio_volume_changed,
};
ctx->mp = libvlc_media_player_new(libvlc, &cbs, ctx);
if (ctx->mp == NULL)
goto error_mlist;
static const struct libvlc_media_player_watch_time_cbs time_cbs = {
.version = 0,
.on_update = player_time_on_update,
.on_paused = player_time_on_paused,
.on_seek = player_time_on_seek,
};
ret = libvlc_media_player_watch_time(ctx->mp, 500000ULL,
&time_cbs, ctx);
if (ret != 0)
goto error_mp;
return 0;
error_mp:
libvlc_media_player_release(ctx->mp);
error_mlist:
libvlc_media_list_release(ctx->mlist);
error_pipe:
pthread_mutex_destroy(&ctx->mainloop_lock);
close(ctx->pipefds[0]);
close(ctx->pipefds[1]);
return -ENOMEM;
}
static int
context_parse_argv(struct context *ctx, int argc, const char **argv)
{
libvlc_media_list_lock(ctx->mlist);
for (int i = 1; i < argc; ++i)
{
const char *path = argv[i];
libvlc_media_t *media;
if (strstr(path, "://") != NULL)
media = libvlc_media_new_location(path);
else
media = libvlc_media_new_path(path);
if (media == NULL)
{
libvlc_media_list_unlock(ctx->mlist);
return -ENOMEM;
}
libvlc_media_list_add_media(ctx->mlist, media);
libvlc_media_release(media); /* Held by the media_list */
}
libvlc_media_t *media = libvlc_media_list_item_at_index(ctx->mlist, 0);
libvlc_media_list_unlock(ctx->mlist);
/* Set the first media */
if (media != NULL)
{
libvlc_media_player_set_media(ctx->mp, media);
libvlc_media_release(media);
}
return 0;
}
static void
setup_terminal(void)
{
#ifdef __linux__
fcntl(STDIN_FILENO, F_SETFL, fcntl(STDIN_FILENO, F_GETFL) | O_NONBLOCK);
/* Avoid pressing enter to read commands */
struct termios oldt, newt;
tcgetattr(STDIN_FILENO, &oldt);
newt = oldt;
newt.c_lflag &= ~(ICANON | ECHO);
tcsetattr(STDIN_FILENO, TCSANOW, &newt);
#endif
}
static void
play_next(struct context *ctx)
{
libvlc_media_list_lock(ctx->mlist);
if (libvlc_media_list_count(ctx->mlist) == ctx->playing_idx + 1)
ctx->playing_idx = 0;
else
ctx->playing_idx++;
libvlc_media_t *media =
libvlc_media_list_item_at_index(ctx->mlist, ctx->playing_idx);
libvlc_media_list_unlock(ctx->mlist);
libvlc_media_player_set_media(ctx->mp, media);
libvlc_media_player_play(ctx->mp);
libvlc_media_release(media);
}
static void
play_previous(struct context *ctx)
{
libvlc_media_list_lock(ctx->mlist);
if (ctx->playing_idx == 0)
ctx->playing_idx = libvlc_media_list_count(ctx->mlist) - 1;
else
ctx->playing_idx--;
libvlc_media_t *media =
libvlc_media_list_item_at_index(ctx->mlist, ctx->playing_idx);
libvlc_media_list_unlock(ctx->mlist);
libvlc_media_player_set_media(ctx->mp, media);
libvlc_media_player_play(ctx->mp);
libvlc_media_release(media);
}
static void
increase_volume(struct context *ctx, int value)
{
int volume = libvlc_audio_get_volume(ctx->mp);
if (volume == -1)
return;
volume += value;
if (volume < 0)
volume = 0;
else if (volume > 200)
volume = 200;
libvlc_audio_set_volume(ctx->mp, volume);
}
static int
mainloop_handle_command(struct context *ctx, const char *command, size_t len)
{
if (len == 1)
{
switch (command[0])
{
case EOF:
case 'q':
return -1;
case 'p':
play_previous(ctx);
break;
case 'n':
play_next(ctx);
break;
case ' ':
libvlc_media_player_pause(ctx->mp);
break;
case 'e':
libvlc_media_player_next_frame(ctx->mp);
break;
default:
print_ui(ctx);
break;
}
}
else if (len > 2 && command[0] == '\033' && command[1] == '[')
{
/* Basic arrow key handling on Linux:
* '\033' + '[' + 'A', 'B', 'C' or 'D' for all 4 keys
* '\033' + "[1;5" + 'A', 'B', 'C' or 'D' for ctrol + 4 keys
*/
bool ctrol_pressed;
char c;
if (len == 6 && strncmp("1;5", &command[2], 3) == 0)
{
c = command[5];
ctrol_pressed = true;
}
else if (len == 3)
{
c = command[2];
ctrol_pressed = false;
}
else
return 0;
libvlc_time_t seek_jump = 1000000; /* 1 second jump (in us) */
int vol_jump = 5; /* 5% jump */
if (ctrol_pressed)
{
seek_jump *= 10; /* 10 seconds jump */
vol_jump = 10; /* 10% jump */
}
switch (c)
{
case 'D':
libvlc_media_player_jump_time(ctx->mp, -seek_jump);
break;
case 'A':
increase_volume(ctx, vol_jump);
break;
case 'C':
libvlc_media_player_jump_time(ctx->mp, seek_jump);
break;
case 'B':
increase_volume(ctx, -vol_jump);
break;
}
}
return 0;
}
static size_t
format_time_us(libvlc_time_t us, char *buffer, size_t buffer_size)
{
time_t seconds = us / 1000000;
struct tm tm;
gmtime_r(&seconds, &tm);
return strftime(buffer, buffer_size, "%k:%M:%S", &tm);
}
static bool
mainloop_display_ui(struct context *ctx)
{
bool run_timer;
pthread_mutex_lock(&ctx->mainloop_lock);
libvlc_time_t ts_us;
double pos;
if (!ctx->time_seeking)
{
libvlc_time_t date = ctx->paused_date > 0 ? ctx->paused_date
: libvlc_clock();
int ret =
libvlc_media_player_time_point_interpolate(&ctx->time_point, date,
&ts_us, &pos);
if (ret != 0)
{
pthread_mutex_unlock(&ctx->mainloop_lock);
return false;
}
run_timer = ctx->paused_date == 0 && ctx->time_point.system_date_us != INT64_MAX;
}
else
{
ts_us = ctx->time_point.ts_us;
pos = ctx->time_point.position;
run_timer = false;
}
float volume = ctx->volume * 100;
pos *= 100;
libvlc_time_t length_us = ctx->time_point.length_us;
pthread_mutex_unlock(&ctx->mainloop_lock);
unsigned time_100ms = ((unsigned)(ts_us / 1000.0) % 1000) / 100;
char buffer_time[256];
size_t len = format_time_us(ts_us, buffer_time, sizeof(buffer_time));
if (len > 0)
{
char buffer_length[256];
len = format_time_us(length_us, buffer_length, sizeof(buffer_length));
fprintf(stdout, "| time: %s.%u / %s | pos: %5.1f%% | vol: %3.0f%%\n",
buffer_time, time_100ms, len > 0 ? buffer_length : "",
pos, volume);
fflush(stdout);
}
return run_timer;
}
static void
mainloop(struct context *ctx)
{
bool run_timer = false;
for (;;)
{
fd_set rfds;
int nfds = 0;
FD_ZERO(&rfds);
FD_SET(STDIN_FILENO, &rfds);
FD_SET(ctx->pipefds[0], &rfds);
assert(ctx->pipefds[0] > STDIN_FILENO);
nfds = ctx->pipefds[0] + 1;
struct timeval tv_buf = {
.tv_sec = 0,
.tv_usec = 100000, /* Update pos/time every 100ms */
};
struct timeval *tv = run_timer ? &tv_buf : NULL;
int retval = select(nfds, &rfds, NULL, NULL, tv);
if (retval == -1)
{
perror("select()");
return;
}
else if (retval == 0)
run_timer = mainloop_display_ui(ctx);
else
{
if (FD_ISSET(STDIN_FILENO, &rfds))
{
char buf[128];
ssize_t len = read(STDIN_FILENO, buf, sizeof(buf));
if (len == -1 && errno != EAGAIN)
{
perror("read()");
return;
}
else if (len == 0)
continue;
retval = mainloop_handle_command(ctx, buf, len);
if (retval == -1)
return;
}
if (FD_ISSET(ctx->pipefds[0], &rfds))
{
char buf;
ssize_t len = read(ctx->pipefds[0], &buf, 1);
if (len == -1)
{
perror("read()");
return;
}
run_timer = mainloop_display_ui(ctx);
}
}
}
}
int
main(int argc, const char **argv)
{
int mainret = EXIT_FAILURE, ret;
/* mandatory to support UTF-8 filenames (provided the locale is well set)*/
setlocale(LC_ALL, "");
if (argc < 2)
{
print_usage(argv[0]);
return EXIT_FAILURE;
}
static const char* const vlcargs[] = {
"--verbose=1",
};
libvlc_instance_t *libvlc = libvlc_new(sizeof vlcargs / sizeof *vlcargs,
vlcargs);
if (libvlc == NULL)
{
fprintf(stderr, "libvlc_new() failed\n");
return EXIT_FAILURE;
}
struct context ctx;
ret = context_init(&ctx, libvlc);
if (ret != 0)
{
libvlc_release(libvlc);
fprintf(stderr, "context_init() failed\n");
return EXIT_FAILURE;
}
ret = context_parse_argv(&ctx, argc, argv);
if (ret != 0)
{
fprintf(stderr, "context_parse_argv() failed\n");
goto error;
}
ret = libvlc_media_player_play(ctx.mp);
if (ret != 0)
{
fprintf(stderr, "libvlc_media_player_play() failed\n");
goto error;
}
setup_terminal();
mainloop(&ctx);
mainret = EXIT_SUCCESS;
error:
libvlc_release(libvlc);
context_destroy(&ctx);
return mainret;
}
¤ Dauer der Verarbeitung: 0.3 Sekunden
(vorverarbeitet am 2026-11-04)
¤
*© Formatika GbR, Deutschland
|
|