mirror of
https://github.com/ZiZc3/VLC.git
synced 2026-10-18 14:01:38 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
82f942e8d6 | ||
|
|
6613c82e5d | ||
|
|
2e211f289e | ||
|
|
016a7421d9 | ||
|
|
3ed7996194 | ||
|
|
19a422b8b8 |
No files matched your search
@@ -10,7 +10,7 @@
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<img alt="Version" src="https://img.shields.io/badge/version-0.1-f37a2b">
|
||||
<img alt="Version" src="https://img.shields.io/badge/version-0.3-f37a2b">
|
||||
<img alt="Platform" src="https://img.shields.io/badge/platform-PS5-1f1f1f">
|
||||
<img alt="VLC" src="https://img.shields.io/badge/libvlc-3.0.24-ff8800">
|
||||
<img alt="Renderer" src="https://img.shields.io/badge/renderer-Vulkan%20(RADV)-c0561b">
|
||||
@@ -188,6 +188,7 @@ Optional full access. With etaHEN, OnionHEN or the Helper, VLC can lift its sand
|
||||
|---|---|
|
||||
| DVD folders and ISOs with menus | ✅ |
|
||||
| Blu-ray folders and ISOs (unprotected) | ✅ |
|
||||
| Blu-ray Java (BD-J) menus | ❌ needs Java, which the PS5 hasn't got: those discs play without menus |
|
||||
| ZIP, RAR and 7z archives open like folders | ✅ |
|
||||
|
||||
## Building
|
||||
|
||||
+2
-2
@@ -48,7 +48,7 @@ done
|
||||
compile "$repo/third_party/imgui/backends/imgui_impl_vulkan.cpp" g++ -std=c++17
|
||||
compile "$repo/third_party/volk/volk.c" gcc -std=c11
|
||||
compile "$repo/src/platform_host.c" gcc -std=gnu11
|
||||
for f in gfx image prefs player library network osub lang web ui main; do
|
||||
for f in gfx image prefs player subconv library network osub lang web ui main; do
|
||||
compile "$repo/src/$f.cc" g++ -std=c++17 -Wno-missing-field-initializers
|
||||
done
|
||||
gcc -O2 -c "$static/static_modules.c" -o "$obj/static_modules.o"
|
||||
@@ -67,5 +67,5 @@ if [[ ${VLCPS5_OWN_IO:-0} == 1 ]]; then
|
||||
objs+=("$obj/libc_missing.o")
|
||||
io_flags=(-Wl,--defsym=readv=xemu_ps5_readv -Wl,--defsym=writev=xemu_ps5_writev)
|
||||
fi
|
||||
g++ "${san[@]}" -o "$out/$name" "${objs[@]}" @"$static/link.rsp" -lpthread -ldl "${io_flags[@]}"
|
||||
g++ "${san[@]}" -o "$out/$name" "${objs[@]}" @"$static/link.rsp" "$prefix/lib/libiconv.a" -lpthread -ldl "${io_flags[@]}"
|
||||
echo "built $out/$name"
|
||||
@@ -0,0 +1,17 @@
|
||||
diff -ru a/modules/access/dvdnav.c b/modules/access/dvdnav.c
|
||||
--- a/modules/access/dvdnav.c
|
||||
+++ b/modules/access/dvdnav.c
|
||||
@@ -89,6 +89,13 @@
|
||||
static int AccessDemuxOpen ( vlc_object_t * );
|
||||
static void Close( vlc_object_t * );
|
||||
|
||||
+/* VLC-PS5: configure looks for dvdnav_stream_cb with the plain preprocessor,
|
||||
+ * without the cross prefix's include path, so it never finds it. libdvdnav
|
||||
+ * 6.1 has it: DVD ISOs on a share or a link need this demux. */
|
||||
+#ifndef HAVE_STREAM_CB_IN_DVDNAV_H
|
||||
+# define HAVE_STREAM_CB_IN_DVDNAV_H 1
|
||||
+#endif
|
||||
+
|
||||
#if DVDREAD_VERSION >= 50300 && defined( HAVE_STREAM_CB_IN_DVDNAV_H )
|
||||
#define HAVE_DVDNAV_DEMUX
|
||||
static int DemuxOpen ( vlc_object_t * );
|
||||
@@ -0,0 +1,35 @@
|
||||
--- a/modules/access/bluray.c 2026-10-09 16:18:09.281680898 +0000
|
||||
+++ b/modules/access/bluray.c 2026-10-09 16:18:09.281979298 +0000
|
||||
@@ -807,6 +807,11 @@
|
||||
var_SetBool( p_demux, "ts-pcr-offsetfix", false );
|
||||
|
||||
var_AddCallback( p_demux->p_input, "intf-event", onIntfEvent, p_demux );
|
||||
+ /* VLC-PS5: whether a menu (HDMV IG or pop-up) is on screen, and whether
|
||||
+ * the film has a pop-up menu, for the app: the d-pad presses its buttons
|
||||
+ * only then (a menu can show in any title, not only "Top Menu"). */
|
||||
+ var_Create( p_demux->p_input, "bluray-menu-open", VLC_VAR_BOOL );
|
||||
+ var_Create( p_demux->p_input, "bluray-popup", VLC_VAR_BOOL );
|
||||
|
||||
/* Open BluRay */
|
||||
#ifdef BLURAY_DEMUX
|
||||
@@ -1012,6 +1017,8 @@
|
||||
demux_sys_t *p_sys = p_demux->p_sys;
|
||||
|
||||
var_DelCallback( p_demux->p_input, "intf-event", onIntfEvent, p_demux );
|
||||
+ var_Destroy( p_demux->p_input, "bluray-menu-open" );
|
||||
+ var_Destroy( p_demux->p_input, "bluray-popup" );
|
||||
|
||||
setTitleInfo(p_sys, NULL);
|
||||
|
||||
@@ -2809,9 +2816,11 @@
|
||||
#endif
|
||||
case BD_EVENT_MENU:
|
||||
p_sys->b_menu_open = e->param;
|
||||
+ var_SetBool( p_demux->p_input, "bluray-menu-open", e->param != 0 );
|
||||
break;
|
||||
case BD_EVENT_POPUP:
|
||||
p_sys->b_popup_available = e->param;
|
||||
+ var_SetBool( p_demux->p_input, "bluray-popup", e->param != 0 );
|
||||
/* TODO: show / hide pop-up menu button in gui ? */
|
||||
break;
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
--- a/modules/text_renderer/freetype/freetype.c 2026-10-09 16:41:20.491780240 +0000
|
||||
+++ b/modules/text_renderer/freetype/freetype.c 2026-10-09 16:41:20.492100340 +0000
|
||||
@@ -1081,6 +1081,33 @@
|
||||
|
||||
return psz_ucs4;
|
||||
}
|
||||
+#elif !defined( HAVE_ICONV )
|
||||
+/* VLC-PS5: no iconv on the console (ToCharset gave nothing back, so plain-text
|
||||
+ * subtitles drew nothing): the UTF-8 is read here, to native-endian UCS-4 as
|
||||
+ * FREETYPE_TO_UCS asks; a bad byte becomes U+FFFD. */
|
||||
+static void *ToUCS4( const char *in, size_t *outsize )
|
||||
+{
|
||||
+ size_t i_len = strlen( in );
|
||||
+ uint32_t *p_out = vlc_alloc( i_len + 1, sizeof( *p_out ) );
|
||||
+ if( unlikely( !p_out ) )
|
||||
+ return NULL;
|
||||
+ size_t i = 0;
|
||||
+ while( *in )
|
||||
+ {
|
||||
+ uint32_t cp;
|
||||
+ size_t i_bytes = vlc_towc( in, &cp );
|
||||
+ if( i_bytes == 0 || i_bytes == (size_t)-1 )
|
||||
+ {
|
||||
+ cp = 0xFFFD;
|
||||
+ i_bytes = 1;
|
||||
+ }
|
||||
+ p_out[i++] = cp;
|
||||
+ in += i_bytes;
|
||||
+ }
|
||||
+ p_out[i] = 0;
|
||||
+ *outsize = i * sizeof( *p_out );
|
||||
+ return p_out;
|
||||
+}
|
||||
#else
|
||||
# define ToUCS4( in, outsize ) ToCharset( FREETYPE_TO_UCS, ( in ), ( outsize ))
|
||||
#endif
|
||||
@@ -0,0 +1,94 @@
|
||||
--- a/src/dvd_input.c
|
||||
+++ b/src/dvd_input.c
|
||||
@@ -103,6 +103,10 @@
|
||||
|
||||
/* dummy file input */
|
||||
int fd;
|
||||
+
|
||||
+ /* VLC-PS5: a stream (DVDOpenStream) without libdvdcss */
|
||||
+ dvd_reader_stream_cb *stream_cb;
|
||||
+ uint64_t pos;
|
||||
};
|
||||
|
||||
static dvd_input_t dvd_input_New(void *priv, dvd_logger_cb *logcb)
|
||||
@@ -199,12 +203,26 @@
|
||||
*/
|
||||
static dvd_input_t file_open(void *priv, dvd_logger_cb *logcb,
|
||||
const char *target,
|
||||
- dvd_reader_stream_cb *stream_cb UNUSED)
|
||||
+ dvd_reader_stream_cb *stream_cb)
|
||||
{
|
||||
dvd_input_t dev;
|
||||
|
||||
- if(target == NULL)
|
||||
- return NULL;
|
||||
+ if(target == NULL) {
|
||||
+ /* VLC-PS5: no libdvdcss, so the plain input reads the stream itself
|
||||
+ * (a DVD image on a network share). Unencrypted discs only. */
|
||||
+ if(priv == NULL || stream_cb == NULL || !stream_cb->pf_seek || !stream_cb->pf_read)
|
||||
+ return NULL;
|
||||
+ dev = dvd_input_New(priv, logcb);
|
||||
+ if(dev == NULL)
|
||||
+ return NULL;
|
||||
+ dev->fd = -1;
|
||||
+ dev->stream_cb = stream_cb;
|
||||
+ if(stream_cb->pf_seek(priv, 0) != 0) {
|
||||
+ free(dev);
|
||||
+ return NULL;
|
||||
+ }
|
||||
+ return dev;
|
||||
+ }
|
||||
/* Allocate the library structure */
|
||||
dev = dvd_input_New(priv, logcb);
|
||||
if(dev == NULL) {
|
||||
@@ -256,6 +274,14 @@
|
||||
{
|
||||
off_t pos;
|
||||
|
||||
+ if(dev->stream_cb) {
|
||||
+ uint64_t to = (uint64_t)blocks * DVD_VIDEO_LB_LEN;
|
||||
+ if(dev->stream_cb->pf_seek(dev->priv, to) != 0)
|
||||
+ return -1;
|
||||
+ dev->pos = to;
|
||||
+ return blocks;
|
||||
+ }
|
||||
+
|
||||
pos = lseek(dev->fd, (off_t)blocks * (off_t)DVD_VIDEO_LB_LEN, SEEK_SET);
|
||||
if(pos < 0) {
|
||||
return pos;
|
||||
@@ -283,6 +309,26 @@
|
||||
len = (size_t)blocks * DVD_VIDEO_LB_LEN;
|
||||
bytes = 0;
|
||||
|
||||
+ if(dev->stream_cb) {
|
||||
+ while(len > 0) {
|
||||
+ int chunk = len > (1 << 30) ? (1 << 30) : (int)len;
|
||||
+ int ret = dev->stream_cb->pf_read(dev->priv, ((char*)buffer) + bytes, chunk);
|
||||
+ if(ret < 0)
|
||||
+ return ret;
|
||||
+ if(ret == 0)
|
||||
+ break;
|
||||
+ len -= ret;
|
||||
+ bytes += ret;
|
||||
+ dev->pos += ret;
|
||||
+ }
|
||||
+ if(bytes % DVD_VIDEO_LB_LEN) {
|
||||
+ /* back to the last whole block, as the file input does */
|
||||
+ dev->pos -= bytes % DVD_VIDEO_LB_LEN;
|
||||
+ dev->stream_cb->pf_seek(dev->priv, dev->pos);
|
||||
+ }
|
||||
+ return (int) (bytes / DVD_VIDEO_LB_LEN);
|
||||
+ }
|
||||
+
|
||||
while(len > 0) {
|
||||
ssize_t ret = read(dev->fd, ((char*)buffer) + bytes, len);
|
||||
|
||||
@@ -318,7 +364,7 @@
|
||||
{
|
||||
int ret;
|
||||
|
||||
- ret = close(dev->fd);
|
||||
+ ret = dev->stream_cb ? 0 : close(dev->fd);
|
||||
|
||||
free(dev);
|
||||
|
||||
+40
-1
@@ -193,7 +193,7 @@ void scan_dir(const std::string &dir, int depth)
|
||||
const char *x = *dot ? dot + 1 : dot;
|
||||
bool video = has_ext(x, video_exts), audio = has_ext(x, audio_exts);
|
||||
bool image = has_ext(x, image_exts), text = has_ext(x, text_exts);
|
||||
bool archive = has_ext(x, archive_exts), iso = !strcasecmp(x, "iso");
|
||||
bool archive = has_ext(x, archive_exts), iso = !strcasecmp(x, "iso") || !strcasecmp(x, "img"); /* a DVD image named .img */
|
||||
if (image) {
|
||||
/* an album's cover file isn't a photo of its own */
|
||||
std::string base = std::string(e->d_name, dot - e->d_name);
|
||||
@@ -1438,6 +1438,45 @@ std::vector<int> library_playlist_items(const std::string &path)
|
||||
return out;
|
||||
}
|
||||
|
||||
int library_playlist_count(const std::string &path)
|
||||
{
|
||||
FILE *f = fopen(path.c_str(), "r");
|
||||
if (!f)
|
||||
return 0;
|
||||
bool pls = path.size() > 4 && strcasecmp(path.c_str() + path.size() - 4, ".pls") == 0;
|
||||
int n = 0;
|
||||
char line[4096];
|
||||
while (fgets(line, sizeof(line), f)) {
|
||||
const char *l = line;
|
||||
while (*l == ' ' || *l == '\t' || (unsigned char)*l == 0xEF || (unsigned char)*l == 0xBB || (unsigned char)*l == 0xBF)
|
||||
l++; /* indent, UTF-8 mark */
|
||||
if (pls ? (!strncasecmp(l, "File", 4) && strchr(l, '=')) : (*l && *l != '#' && *l != '\r' && *l != '\n'))
|
||||
n++;
|
||||
}
|
||||
fclose(f);
|
||||
return n;
|
||||
}
|
||||
|
||||
bool library_playlist_has_links(const std::string &path)
|
||||
{
|
||||
FILE *f = fopen(path.c_str(), "r");
|
||||
if (!f)
|
||||
return false;
|
||||
bool links = false;
|
||||
char line[4096];
|
||||
while (!links && fgets(line, sizeof(line), f)) {
|
||||
const char *l = line;
|
||||
if (*l == '#')
|
||||
continue;
|
||||
if (!strncasecmp(l, "File", 4) && strchr(l, '='))
|
||||
l = strchr(l, '=') + 1; /* .pls */
|
||||
const char *scheme = strstr(l, "://");
|
||||
links = scheme && scheme > l && strncasecmp(l, "file://", 7) != 0;
|
||||
}
|
||||
fclose(f);
|
||||
return links;
|
||||
}
|
||||
|
||||
/* ---- playlists made in VLC -------------------------------------------------------- */
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -69,6 +69,10 @@ MediaItem *library_find(const std::string &path);
|
||||
std::vector<PlaylistFile> &library_playlists();
|
||||
/* The library files a playlist names, in its order (others are skipped). */
|
||||
std::vector<int> library_playlist_items(const std::string &path);
|
||||
/* Every entry it names, links and files alike. */
|
||||
int library_playlist_count(const std::string &path);
|
||||
/* It names links (http://, rtp://...): a channel list, opened in Browse. */
|
||||
bool library_playlist_has_links(const std::string &path);
|
||||
/* Playlists made in VLC: <media>/Playlists/<name>.m3u8, one absolute path a
|
||||
* line (with #EXTINF names), so VLC on a computer reads them too. Only these
|
||||
* are changed; ones found elsewhere just play. */
|
||||
|
||||
+8
-2
@@ -287,8 +287,14 @@ void finish_listing()
|
||||
libvlc_media_list_unlock(subs);
|
||||
libvlc_media_list_release(subs);
|
||||
}
|
||||
/* Folders A-Z; a playlist (TV channels, radio) keeps its own order. */
|
||||
bool playlist = list_url.compare(0, 4, "http") == 0;
|
||||
/* Folders A-Z; a playlist (TV channels, radio, an .m3u on a share)
|
||||
* keeps its own order. */
|
||||
std::string bare = list_url.substr(0, list_url.find('?'));
|
||||
size_t dot = bare.rfind('.');
|
||||
std::string ext = dot == std::string::npos ? "" : bare.substr(dot + 1);
|
||||
for (char &c : ext)
|
||||
c = (char)tolower((unsigned char)c);
|
||||
bool playlist = list_url.compare(0, 4, "http") == 0 || ext == "m3u" || ext == "m3u8" || ext == "pls";
|
||||
if (!playlist)
|
||||
std::stable_sort(entries.begin(), entries.end(), [](const NetEntry &a, const NetEntry &b) {
|
||||
if (a.dir != b.dir)
|
||||
|
||||
+1
-1
@@ -32,7 +32,7 @@
|
||||
namespace {
|
||||
|
||||
const char *API_HOST = "api.opensubtitles.com";
|
||||
const char *USER_AGENT = "VLC-PS5 v0.1";
|
||||
const char *USER_AGENT = "VLC-PS5 v" VLC_PS5_VERSION;
|
||||
|
||||
/* ---- what the interface sees ---- */
|
||||
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ extern "C" {
|
||||
#endif
|
||||
|
||||
#define VLC_PS5_TITLE_ID "PPSA85300"
|
||||
#define VLC_PS5_VERSION "0.1"
|
||||
#define VLC_PS5_VERSION "0.3"
|
||||
|
||||
/* DualSense buttons (scePadRead's layout, as QUICK3 and PS5_vkQuake use it),
|
||||
* plus the triggers past half as buttons. */
|
||||
|
||||
+567
-14
@@ -19,7 +19,9 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <ctype.h>
|
||||
#include <dirent.h>
|
||||
#include <math.h>
|
||||
#include <condition_variable>
|
||||
#include <deque>
|
||||
@@ -37,6 +39,7 @@
|
||||
#include <vlc_common.h>
|
||||
#include <vlc_variables.h>
|
||||
#include <vlc_url.h>
|
||||
#include <vlc_stream.h>
|
||||
|
||||
#include "gen/video_frag.h"
|
||||
#include "gen/video_vert.h"
|
||||
@@ -45,6 +48,7 @@
|
||||
#include "lang.h"
|
||||
#include "platform.h"
|
||||
#include "prefs.h"
|
||||
#include "subconv.h"
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -87,6 +91,24 @@ bool restore_delays;
|
||||
double sub_check_at;
|
||||
size_t sub_check_count;
|
||||
bool sub_attach_all; /* the reopen: every added file, wherever it is */
|
||||
/* Subtitle files beside a video on a share or a link, found in the background
|
||||
* once it plays (src: side_subs_find). */
|
||||
struct SideSubs {
|
||||
std::mutex lock;
|
||||
std::string path; /* the video they're for */
|
||||
std::vector<std::string> uris; /* best first */
|
||||
bool ready;
|
||||
int serial; /* bumped by every open: an old search is dropped */
|
||||
} side;
|
||||
std::vector<std::string> source_options; /* the open's login options, for the folder listing */
|
||||
/* Subtitle files added by hand from a share or a link, read in the background:
|
||||
* player_tick puts them in (when it's still the same file). */
|
||||
struct PendingSub {
|
||||
std::string path, ready; /* ready: what VLC gets, "" = couldn't be read */
|
||||
int serial; /* side.serial when it was asked for */
|
||||
};
|
||||
std::mutex pending_lock;
|
||||
std::vector<PendingSub> pending_subs;
|
||||
|
||||
/* ---- pictures -------------------------------------------------------------- */
|
||||
|
||||
@@ -900,6 +922,7 @@ void apply_saved_choices()
|
||||
|
||||
std::vector<Chapter> chapters_cache;
|
||||
double chapters_tried;
|
||||
int chapters_title = -2; /* a disc's chapters belong to its current title */
|
||||
|
||||
/* A slot's picture as RGB, cropped to what's visible: the shader's sums on
|
||||
* the CPU (BT.709 for HD, 601 below). */
|
||||
@@ -967,9 +990,18 @@ bool player_init()
|
||||
/* HLS / DASH segments through the access modules: ps5http (the
|
||||
* console's HTTP service), not the adaptive module's own sockets. */
|
||||
"--adaptive-use-access",
|
||||
/* No iconv on the console: subtitles are read as UTF-8 (VLC's CP1252
|
||||
* fallback only logged "cannot convert" for every file). */
|
||||
"--subsdec-encoding=UTF-8",
|
||||
#endif
|
||||
"--ignore-config",
|
||||
"--no-video-title-show",
|
||||
/* Browse shows every file: VLC's listings (SMB, DLNA, ZIP/RAR) too, not
|
||||
* only local folders. VLC hid m3u, images, subtitles, txt... there. */
|
||||
"--ignore-filetypes=",
|
||||
/* Subtitle files beside a video: our own search, for every source
|
||||
* (side_subs_find); VLC's looked beside local files only. */
|
||||
"--no-sub-autodetect-file",
|
||||
"--stats", /* decoded/shown/lost pictures, for player_tick */
|
||||
"--aout=adummy", /* replaced by our callbacks (World 1) */
|
||||
"--avcodec-threads=0", /* one per core */
|
||||
@@ -1084,13 +1116,327 @@ bool player_open(const std::string &path, int64_t start_ms, const std::vector<st
|
||||
return false;
|
||||
for (const std::string &o : options)
|
||||
libvlc_media_add_option(m, o.c_str());
|
||||
return player_open_media(m, path, start_ms);
|
||||
return player_open_media(m, path, start_ms, options);
|
||||
}
|
||||
|
||||
std::string file_uri(const std::string &path);
|
||||
|
||||
bool player_open_media(libvlc_media_t *m, const std::string &path, int64_t start_ms)
|
||||
/* A subtitle file's address for VLC: a share's or a link's as it is, a
|
||||
* local path made a file:// URI. */
|
||||
std::string sub_uri(const std::string &path)
|
||||
{
|
||||
return path.find("://") != std::string::npos ? path : file_uri(path);
|
||||
}
|
||||
|
||||
extern "C" input_thread_t *libvlc_get_input_thread(libvlc_media_player_t *);
|
||||
|
||||
/* The language a subtitle file is most likely in, for one that isn't UTF-8:
|
||||
* the subtitle language chosen, else the menus'. */
|
||||
std::string sub_lang_hint()
|
||||
{
|
||||
std::string s = player_sub_language();
|
||||
return s.empty() || s == "off" || s == "none" ? std::string(lang_code(lang_current())) : s;
|
||||
}
|
||||
|
||||
/* A subtitle file on a share or a link, read through VLC as a child of the
|
||||
* playing input (so a share's login comes with it); at most 8 MiB. False
|
||||
* when it can't be opened (a link's guess that isn't there). */
|
||||
bool read_remote_sub(const std::string &uri, std::string *out)
|
||||
{
|
||||
input_thread_t *input = nullptr;
|
||||
/* the open may still be on its way */
|
||||
for (int i = 0; i < 50 && !(input = libvlc_get_input_thread(mp)); i++)
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
if (!input)
|
||||
return false;
|
||||
stream_t *s = vlc_stream_NewURL((vlc_object_t *)input, uri.c_str());
|
||||
bool ok = s != nullptr;
|
||||
out->clear();
|
||||
if (s) {
|
||||
char buf[65536];
|
||||
ssize_t n;
|
||||
while ((n = vlc_stream_Read(s, buf, sizeof(buf))) > 0 && out->size() < (8u << 20))
|
||||
out->append(buf, (size_t)n);
|
||||
vlc_stream_Delete(s);
|
||||
ok = !out->empty();
|
||||
}
|
||||
vlc_object_release((vlc_object_t *)input);
|
||||
return ok;
|
||||
}
|
||||
|
||||
/* A subtitle file ready for VLC: a local path, or the address of one on a
|
||||
* share or a link; one that isn't UTF-8 becomes a converted local copy
|
||||
* (the console's VLC has no iconv). "" when a remote one can't be read. */
|
||||
std::string sub_ready(const std::string &path, const std::string &hint)
|
||||
{
|
||||
if (path.find("://") == std::string::npos)
|
||||
return subconv_local(path, hint);
|
||||
std::string bytes;
|
||||
if (!read_remote_sub(path, &bytes))
|
||||
return "";
|
||||
std::string b = path.substr(0, path.find_first_of("?#"));
|
||||
char *name = vlc_uri_decode_duplicate(b.substr(b.rfind('/') + 1).c_str());
|
||||
std::string copy = subconv_bytes(bytes, name ? name : "subtitle.srt", hint);
|
||||
free(name);
|
||||
return copy.empty() ? path : copy;
|
||||
}
|
||||
|
||||
/* ---- subtitles beside the video ------------------------------------------------
|
||||
* Ours, the same for every source: VLC's own search only looks beside local
|
||||
* files, and the ones its share listings attach weren't turned on. A
|
||||
* subtitle file goes with a video when its name starts with the video's
|
||||
* (without the extension) followed by nothing or a separator: "Film.srt",
|
||||
* "Film.en.srt", "Film - English.ass". Local folders and shares (SMB, FTP,
|
||||
* SFTP, NFS) are listed; a link (http, https) is asked for Film.srt. */
|
||||
|
||||
bool is_sub_name(const std::string &lower_name)
|
||||
{
|
||||
static const char *const exts[] = { "srt", "ass", "ssa", "vtt", "sub", "idx", "smi", "sami", "ttml",
|
||||
"usf", "jss", "psb", "rt", "mpl2", "pjs", "stl", "dks" };
|
||||
size_t dot = lower_name.rfind('.');
|
||||
if (dot == std::string::npos)
|
||||
return false;
|
||||
for (const char *e : exts)
|
||||
if (lower_name.compare(dot + 1, std::string::npos, e) == 0)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string lowered(std::string s)
|
||||
{
|
||||
for (char &c : s)
|
||||
c = (char)tolower((unsigned char)c);
|
||||
return s;
|
||||
}
|
||||
|
||||
/* name (a file's) goes with the video named base (no extension), both lowercase */
|
||||
bool sub_goes_with(const std::string &base, const std::string &name)
|
||||
{
|
||||
if (base.empty() || name.size() <= base.size() || name.compare(0, base.size(), base) != 0 ||
|
||||
!is_sub_name(name))
|
||||
return false;
|
||||
char c = name[base.size()];
|
||||
return c == '.' || c == ' ' || c == '-' || c == '_' || c == '[' || c == '(';
|
||||
}
|
||||
|
||||
/* The matches, best first: Film.srt, then the others by name; a VobSub .sub
|
||||
* goes only through its .idx. */
|
||||
std::vector<std::string> sort_side_subs(const std::string &base, std::vector<std::string> names)
|
||||
{
|
||||
std::vector<std::string> lower;
|
||||
for (const std::string &n : names)
|
||||
lower.push_back(lowered(n));
|
||||
std::vector<size_t> order;
|
||||
for (size_t i = 0; i < names.size(); i++) {
|
||||
const std::string &l = lower[i];
|
||||
if (l.size() > 4 && !l.compare(l.size() - 4, 4, ".sub") &&
|
||||
std::find(lower.begin(), lower.end(), l.substr(0, l.size() - 4) + ".idx") != lower.end())
|
||||
continue;
|
||||
order.push_back(i);
|
||||
}
|
||||
std::string exact = base + ".srt";
|
||||
std::stable_sort(order.begin(), order.end(), [&](size_t a, size_t b) {
|
||||
bool ea = lower[a] == exact, eb = lower[b] == exact;
|
||||
if (ea != eb)
|
||||
return ea;
|
||||
return lower[a] < lower[b];
|
||||
});
|
||||
std::vector<std::string> out;
|
||||
for (size_t i : order)
|
||||
out.push_back(names[i]);
|
||||
return out;
|
||||
}
|
||||
|
||||
/* A local video: its folder and the usual subtitle folders inside it. */
|
||||
std::vector<std::string> local_side_subs(const std::string &path)
|
||||
{
|
||||
size_t slash = path.rfind('/');
|
||||
if (slash == std::string::npos)
|
||||
return {};
|
||||
std::string dir = path.substr(0, slash), file = path.substr(slash + 1);
|
||||
std::string base = lowered(file.substr(0, file.rfind('.')));
|
||||
std::vector<std::string> found;
|
||||
static const char *const subdirs[] = { "", "/Subs", "/subs", "/Subtitles", "/subtitles", "/Sub", "/sub" };
|
||||
for (const char *sd : subdirs) {
|
||||
std::string d = dir + sd;
|
||||
DIR *h = opendir(d.c_str());
|
||||
if (!h)
|
||||
continue;
|
||||
std::vector<std::string> names;
|
||||
while (struct dirent *e = readdir(h))
|
||||
if (e->d_name[0] != '.' && sub_goes_with(base, lowered(e->d_name)))
|
||||
names.push_back(e->d_name);
|
||||
closedir(h);
|
||||
for (const std::string &n : sort_side_subs(base, names)) {
|
||||
std::string full = d + "/" + n;
|
||||
if (std::find(found.begin(), found.end(), full) == found.end())
|
||||
found.push_back(full);
|
||||
}
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
bool url_scheme_is(const std::string &url, const char *scheme)
|
||||
{
|
||||
size_t n = strlen(scheme);
|
||||
return url.size() > n + 3 && !strncasecmp(url.c_str(), scheme, n) && !url.compare(n, 3, "://");
|
||||
}
|
||||
|
||||
/* A share or a link: in the background, then player_tick adds what it found. */
|
||||
void side_subs_find(const std::string &url, int serial)
|
||||
{
|
||||
std::string bare = url.substr(0, url.find_first_of("?#"));
|
||||
size_t slash = bare.rfind('/');
|
||||
if (slash == std::string::npos || slash < bare.find("://") + 3)
|
||||
return;
|
||||
std::string parent = bare.substr(0, slash + 1), last = bare.substr(slash + 1);
|
||||
char *dec = vlc_uri_decode_duplicate(last.c_str());
|
||||
std::string file = dec ? dec : last;
|
||||
free(dec);
|
||||
size_t dot = file.rfind('.');
|
||||
if (dot == std::string::npos || dot == 0)
|
||||
return; /* a stream's address, not a file */
|
||||
std::string ext = lowered(file.substr(dot + 1));
|
||||
if (ext == "m3u8" || ext == "m3u" || ext == "mpd" || ext == "pls" || ext == "iso" || ext == "img" || is_sub_name(lowered(file)))
|
||||
return;
|
||||
std::string base = lowered(file.substr(0, dot));
|
||||
bool listable = url_scheme_is(url, "smb") || url_scheme_is(url, "ftp") || url_scheme_is(url, "ftps") ||
|
||||
url_scheme_is(url, "sftp") || url_scheme_is(url, "nfs");
|
||||
bool link = url_scheme_is(url, "http") || url_scheme_is(url, "https");
|
||||
if (!listable && !link)
|
||||
return;
|
||||
std::vector<std::string> opts = source_options;
|
||||
/* its own reference: a quit during the listing (up to 8 s) mustn't free
|
||||
* VLC under it */
|
||||
libvlc_instance_t *inst = vlc;
|
||||
libvlc_retain(inst);
|
||||
std::string hint = sub_lang_hint();
|
||||
std::thread([=]() {
|
||||
std::vector<std::string> uris;
|
||||
if (link) {
|
||||
/* No folder to list: the one most links have beside them. */
|
||||
uris.push_back(bare.substr(0, bare.size() - (file.size() - dot)) + ".srt");
|
||||
(void)base;
|
||||
} else if (libvlc_media_t *m = libvlc_media_new_location(inst, parent.c_str())) {
|
||||
for (const std::string &o : opts)
|
||||
libvlc_media_add_option(m, o.c_str());
|
||||
libvlc_media_parse_with_options(m, libvlc_media_parse_network, 8000);
|
||||
for (int i = 0; i < 100 && libvlc_media_get_parsed_status(m) == 0; i++)
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
std::vector<std::string> names, mrls;
|
||||
if (libvlc_media_list_t *list = libvlc_media_subitems(m)) {
|
||||
libvlc_media_list_lock(list);
|
||||
for (int i = 0; i < libvlc_media_list_count(list); i++) {
|
||||
libvlc_media_t *c = libvlc_media_list_item_at_index(list, i);
|
||||
if (!c)
|
||||
continue;
|
||||
if (char *mrl = libvlc_media_get_mrl(c)) {
|
||||
std::string u = mrl;
|
||||
std::string b = u.substr(0, u.find_first_of("?#"));
|
||||
char *n = vlc_uri_decode_duplicate(b.substr(b.rfind('/') + 1).c_str());
|
||||
if (n && sub_goes_with(base, lowered(n))) {
|
||||
names.push_back(n);
|
||||
mrls.push_back(u);
|
||||
}
|
||||
free(n);
|
||||
free(mrl);
|
||||
}
|
||||
libvlc_media_release(c);
|
||||
}
|
||||
libvlc_media_list_unlock(list);
|
||||
libvlc_media_list_release(list);
|
||||
}
|
||||
libvlc_media_release(m);
|
||||
for (const std::string &n : sort_side_subs(base, names))
|
||||
uris.push_back(mrls[std::find(names.begin(), names.end(), n) - names.begin()]);
|
||||
}
|
||||
/* read through VLC: one that isn't UTF-8 becomes a converted copy,
|
||||
* a link's guess that isn't there is dropped */
|
||||
std::vector<std::string> ready;
|
||||
for (const std::string &u : uris) {
|
||||
std::string r = sub_ready(u, hint);
|
||||
if (!r.empty())
|
||||
ready.push_back(r.find("://") == std::string::npos ? file_uri(r) : r);
|
||||
}
|
||||
uris.swap(ready);
|
||||
libvlc_release(inst);
|
||||
fprintf(stderr, "player: %zu subtitle file(s) beside %s\n", uris.size(), url.c_str());
|
||||
std::lock_guard<std::mutex> g(side.lock);
|
||||
if (side.serial != serial)
|
||||
return;
|
||||
side.path = url;
|
||||
side.uris = uris;
|
||||
side.ready = true;
|
||||
}).detach();
|
||||
}
|
||||
|
||||
/* Found ones go in once the video plays: the first turned on (unless
|
||||
* subtitles are set to Off), the others listed in Tracks. */
|
||||
void side_subs_apply()
|
||||
{
|
||||
std::vector<std::string> uris;
|
||||
{
|
||||
std::lock_guard<std::mutex> g(side.lock);
|
||||
if (!side.ready || side.path != current_path)
|
||||
return;
|
||||
side.ready = false;
|
||||
uris = side.uris;
|
||||
}
|
||||
/* A media from a listing opened again still has the ones that loaded
|
||||
* last time (VLC keeps them on the item): not twice. */
|
||||
std::vector<std::string> have;
|
||||
if (libvlc_media_t *m = libvlc_media_player_get_media(mp)) {
|
||||
libvlc_media_slave_t **slaves = nullptr;
|
||||
unsigned n = libvlc_media_slaves_get(m, &slaves);
|
||||
for (unsigned i = 0; i < n; i++)
|
||||
if (slaves[i]->psz_uri)
|
||||
have.push_back(slaves[i]->psz_uri);
|
||||
libvlc_media_slaves_release(slaves, n);
|
||||
libvlc_media_release(m);
|
||||
}
|
||||
bool on = player_sub_language() != "off";
|
||||
for (size_t i = 0; i < uris.size(); i++) {
|
||||
if (std::find(have.begin(), have.end(), uris[i]) != have.end()) {
|
||||
fprintf(stderr, "player: subtitle beside the video %s: already there\n", uris[i].c_str());
|
||||
continue;
|
||||
}
|
||||
int r = libvlc_media_player_add_slave(mp, libvlc_media_slave_type_subtitle, uris[i].c_str(), on && i == 0);
|
||||
fprintf(stderr, "player: subtitle beside the video %s: %s\n", uris[i].c_str(), r == 0 ? "added" : "failed");
|
||||
}
|
||||
}
|
||||
|
||||
bool player_open_media(libvlc_media_t *m, const std::string &path, int64_t start_ms,
|
||||
const std::vector<std::string> &options)
|
||||
{
|
||||
/* A reopen of the same file (Auto's, Watch again) keeps its login. */
|
||||
if (!options.empty() || path != current_path)
|
||||
source_options = options;
|
||||
{
|
||||
std::lock_guard<std::mutex> g(side.lock);
|
||||
side.serial++;
|
||||
side.ready = false;
|
||||
}
|
||||
bool url = path.find("://") != std::string::npos;
|
||||
std::string local = path;
|
||||
if (url_scheme_is(path, "file")) {
|
||||
char *p = vlc_uri2path(path.c_str());
|
||||
local = p ? p : "";
|
||||
free(p);
|
||||
url = false;
|
||||
}
|
||||
if (url) {
|
||||
side_subs_find(path, side.serial);
|
||||
} else if (!local.empty()) {
|
||||
std::vector<std::string> subs = local_side_subs(local);
|
||||
bool on = player_sub_language() != "off";
|
||||
std::string hint = sub_lang_hint();
|
||||
for (size_t i = 0; i < subs.size(); i++) {
|
||||
/* 4 (the user's): VLC turns the first one on; 3: only listed */
|
||||
libvlc_media_slaves_add(m, libvlc_media_slave_type_subtitle, on && i == 0 ? 4 : 3,
|
||||
file_uri(sub_ready(subs[i], hint)).c_str());
|
||||
fprintf(stderr, "player: subtitle beside the video: %s\n", subs[i].c_str());
|
||||
}
|
||||
}
|
||||
if (path != kept.path) {
|
||||
/* another file: upright, no delays, its own tracks */
|
||||
picture.rotate = 0;
|
||||
@@ -1098,11 +1444,11 @@ bool player_open_media(libvlc_media_t *m, const std::string &path, int64_t start
|
||||
kept = { path, 0, 0, -2, -2, {} };
|
||||
} else {
|
||||
/* the same one again: what was set comes back once it plays */
|
||||
/* (VLC finds the ones beside the video, named like it, by itself) */
|
||||
/* (the ones beside the video, named like it, are found again) */
|
||||
std::string base = path.substr(0, path.rfind('.'));
|
||||
for (const std::string &sub : kept.subs)
|
||||
if (sub_attach_all || sub.compare(0, base.size(), base) != 0)
|
||||
libvlc_media_slaves_add(m, libvlc_media_slave_type_subtitle, 4, file_uri(sub).c_str());
|
||||
libvlc_media_slaves_add(m, libvlc_media_slave_type_subtitle, 4, sub_uri(sub).c_str());
|
||||
if (kept.audio != -2)
|
||||
restore_audio = kept.audio;
|
||||
if (kept.spu != -2)
|
||||
@@ -1121,6 +1467,8 @@ bool player_open_media(libvlc_media_t *m, const std::string &path, int64_t start
|
||||
* (PS5 recordings, streaming encodes) at 60 fps, that hidden work froze the
|
||||
* picture after every jump on the console. */
|
||||
libvlc_media_add_option(m, ":input-fast-seek");
|
||||
/* A Blu-ray checks the player's region (VLC's default is B, Europe): ours. */
|
||||
libvlc_media_add_option(m, (":bluray-region=" + player_bluray_region()).c_str());
|
||||
std::string alang = player_audio_language(), slang = player_sub_language();
|
||||
if (!alang.empty())
|
||||
libvlc_media_add_option(m, (":audio-language=" + alang).c_str());
|
||||
@@ -1128,6 +1476,7 @@ bool player_open_media(libvlc_media_t *m, const std::string &path, int64_t start
|
||||
libvlc_media_add_option(m, (":sub-language=" + (slang == "off" ? std::string("none") : slang)).c_str());
|
||||
chapters_cache.clear();
|
||||
chapters_tried = 0;
|
||||
chapters_title = -2;
|
||||
fast_now = decode_mode == DECODE_FAST || (decode_mode == DECODE_AUTO && path == fast_path);
|
||||
if (fast_now) {
|
||||
libvlc_media_add_option(m, ":avcodec-skiploopfilter=4");
|
||||
@@ -1169,12 +1518,36 @@ bool player_fast_decoding()
|
||||
return fast_now;
|
||||
}
|
||||
|
||||
bool add_ready_subtitle(const std::string &path, const std::string &ready);
|
||||
|
||||
/* The file playing is a disc: an image (.iso, .img), dvd:// or bluray://, or
|
||||
* anything VLC sees several titles in (a VIDEO_TS / BDMV folder). */
|
||||
bool playing_disc_now()
|
||||
{
|
||||
std::string bare = lowered(current_path.substr(0, current_path.find('?')));
|
||||
if (bare.size() > 4 && (!bare.compare(bare.size() - 4, 4, ".iso") || !bare.compare(bare.size() - 4, 4, ".img")))
|
||||
return true;
|
||||
if (url_scheme_is(bare, "dvd") || url_scheme_is(bare, "bluray"))
|
||||
return true;
|
||||
return mp && libvlc_media_player_get_title_count(mp) > 1;
|
||||
}
|
||||
|
||||
PlayerEvent player_tick()
|
||||
{
|
||||
double t = plat_time();
|
||||
std::vector<PendingSub> done;
|
||||
{
|
||||
std::lock_guard<std::mutex> g(pending_lock);
|
||||
done.swap(pending_subs);
|
||||
}
|
||||
for (const PendingSub &p : done)
|
||||
if (mp && p.serial == side.serial)
|
||||
add_ready_subtitle(p.path, p.ready);
|
||||
if (!mp || t < health.next)
|
||||
return PLAYER_EVENT_NONE;
|
||||
health.next = t + 1;
|
||||
if (state() == libvlc_Playing)
|
||||
side_subs_apply();
|
||||
if (sub_check_at > 0 && t >= sub_check_at && state() == libvlc_Playing) {
|
||||
sub_check_at = 0;
|
||||
size_t now_count = player_subtitle_tracks().size();
|
||||
@@ -1231,6 +1604,11 @@ PlayerEvent player_tick()
|
||||
/* Struggling: most pictures of a second lost, or far fewer shown than the
|
||||
* file has. Not while paused, fast-forwarding or with no video at all. */
|
||||
bool playing = state() == libvlc_Playing && libvlc_media_player_get_rate(mp) <= 1.01f;
|
||||
/* Not on a disc: its menus and still screens show few pictures by design
|
||||
* (a Blu-ray menu looked like "can't keep up" every second), and Auto's
|
||||
* reopen would throw the menus away. */
|
||||
if (playing_disc_now())
|
||||
playing = false;
|
||||
bool bad = playing && decoded > 0 &&
|
||||
(lost > shown || shown < fps * 0.5f);
|
||||
health.bad_seconds = bad ? health.bad_seconds + 1 : 0;
|
||||
@@ -1310,6 +1688,86 @@ void player_navigate(int action)
|
||||
libvlc_media_player_navigate(mp, modes[action]);
|
||||
}
|
||||
|
||||
/* libvlc's own (lib/media_player_internal.h): the input, held. */
|
||||
extern "C" input_thread_t *libvlc_get_input_thread(libvlc_media_player_t *);
|
||||
|
||||
bool player_in_menu()
|
||||
{
|
||||
/* Read four times a second: the title list is copied out on every call. */
|
||||
static double next;
|
||||
static bool menu;
|
||||
double now = plat_time();
|
||||
if (now < next)
|
||||
return menu;
|
||||
next = now + 0.25;
|
||||
menu = false;
|
||||
if (!mp)
|
||||
return menu;
|
||||
/* A DVD says when menu buttons are on screen ("highlight", set by VLC's
|
||||
* dvdnav). A DVD's intro or warning screen is in its menu part too but has
|
||||
* no buttons: there ✕ must pause, not press a button nobody sees. */
|
||||
if (input_thread_t *input = libvlc_get_input_thread(mp)) {
|
||||
vlc_object_t *in = (vlc_object_t *)input; /* input_thread_t is opaque here */
|
||||
/* A Blu-ray says when its menu is on screen, in any title
|
||||
* (patches/0010); the "Top Menu" title alone missed most discs. Asked
|
||||
* first: VLC's subtitle unit puts a "highlight" on every input with
|
||||
* a video output, so that one alone doesn't mean a DVD. */
|
||||
bool bd = var_Type(in, "bluray-menu-open") != 0;
|
||||
bool dvd = !bd && var_Type(in, "highlight") != 0;
|
||||
if (dvd)
|
||||
menu = var_GetBool(in, "highlight");
|
||||
else if (bd)
|
||||
menu = var_GetBool(in, "bluray-menu-open");
|
||||
vlc_object_release(in);
|
||||
static int logged = -1;
|
||||
if ((dvd || bd) && logged != (int)menu) {
|
||||
logged = menu;
|
||||
fprintf(stderr, "player: %s menu %s\n", dvd ? "DVD" : "Blu-ray", menu ? "on screen" : "gone");
|
||||
}
|
||||
if (dvd || bd)
|
||||
return menu;
|
||||
}
|
||||
/* A Blu-ray played without menus: its menu titles. */
|
||||
int cur = libvlc_media_player_get_title(mp);
|
||||
libvlc_title_description_t **titles = nullptr;
|
||||
int n = libvlc_media_player_get_full_title_descriptions(mp, &titles);
|
||||
if (n > 0 && cur >= 0 && cur < n && titles[cur])
|
||||
/* menu only: a Blu-ray marks its movie "interactive" too (pop-up
|
||||
* menus), and ✕ must still pause that */
|
||||
menu = (titles[cur]->i_flags & libvlc_title_menu) != 0;
|
||||
if (n > 0)
|
||||
libvlc_title_descriptions_release(titles, (unsigned)n);
|
||||
return menu;
|
||||
}
|
||||
|
||||
/* The disc's main menu (a DVD's root menu, a Blu-ray's Top Menu) or a
|
||||
* Blu-ray's pop-up menu, over the film. */
|
||||
void player_disc_menu(bool popup)
|
||||
{
|
||||
if (!mp)
|
||||
return;
|
||||
if (input_thread_t *input = libvlc_get_input_thread(mp)) {
|
||||
vlc_object_t *in = (vlc_object_t *)input;
|
||||
const char *var = popup ? "menu-popup" : "menu-title";
|
||||
if (var_Type(in, var) != 0)
|
||||
var_TriggerCallback(in, var);
|
||||
fprintf(stderr, "player: disc %s\n", popup ? "pop-up menu" : "menu");
|
||||
vlc_object_release(in);
|
||||
}
|
||||
}
|
||||
|
||||
bool player_disc_has_popup()
|
||||
{
|
||||
bool popup = false;
|
||||
if (mp)
|
||||
if (input_thread_t *input = libvlc_get_input_thread(mp)) {
|
||||
vlc_object_t *in = (vlc_object_t *)input;
|
||||
popup = var_Type(in, "bluray-popup") != 0 && var_GetBool(in, "bluray-popup");
|
||||
vlc_object_release(in);
|
||||
}
|
||||
return popup;
|
||||
}
|
||||
|
||||
bool player_paused()
|
||||
{
|
||||
if (live_paused)
|
||||
@@ -1350,7 +1808,14 @@ void player_toggle_pause()
|
||||
return;
|
||||
}
|
||||
bool want = !player_paused();
|
||||
fprintf(stderr, "player: %s\n", want ? "pause" : "play");
|
||||
libvlc_media_player_set_pause(mp, want ? 1 : 0);
|
||||
if (!want) {
|
||||
/* the second that plays on from a pause starts slowly: not a
|
||||
* "can't keep up" (the count starts again after it) */
|
||||
health.primed = false;
|
||||
health.next = plat_time() + 2;
|
||||
}
|
||||
pause_wanted = want ? 1 : 0;
|
||||
pause_wanted_at = plat_time();
|
||||
std::lock_guard<std::mutex> g(audio_lock);
|
||||
@@ -1480,16 +1945,57 @@ std::string file_uri(const std::string &path)
|
||||
return uri;
|
||||
}
|
||||
|
||||
bool player_is_subtitle_name(const std::string &name)
|
||||
{
|
||||
return is_sub_name(lowered(name));
|
||||
}
|
||||
|
||||
bool player_add_subtitle(const std::string &path)
|
||||
{
|
||||
if (!mp)
|
||||
return false;
|
||||
/* A full path or an address only: VLC makes a relative one full with
|
||||
* getcwd(), which crashed on the console. */
|
||||
if (path.empty() || (path[0] != '/' && path.find("://") == std::string::npos)) {
|
||||
fprintf(stderr, "player: not a subtitle file address: \"%s\"\n", path.c_str());
|
||||
return false;
|
||||
}
|
||||
if (path.find("://") != std::string::npos) {
|
||||
/* On a share or a link: read (and made UTF-8) in the background, put
|
||||
* in by player_tick; read here, the picture froze ~0.6 s. */
|
||||
int serial = side.serial;
|
||||
std::string hint = sub_lang_hint();
|
||||
libvlc_instance_t *inst = vlc;
|
||||
libvlc_retain(inst);
|
||||
std::thread([=]() {
|
||||
std::string ready = sub_ready(path, hint);
|
||||
libvlc_release(inst);
|
||||
std::lock_guard<std::mutex> g(pending_lock);
|
||||
pending_subs.push_back({ path, ready, serial });
|
||||
}).detach();
|
||||
return true;
|
||||
}
|
||||
/* not UTF-8: a converted copy */
|
||||
return add_ready_subtitle(path, sub_ready(path, sub_lang_hint()));
|
||||
}
|
||||
|
||||
/* A subtitle file read and made ready: into the file playing. */
|
||||
bool add_ready_subtitle(const std::string &path, const std::string &ready)
|
||||
{
|
||||
if (ready.empty()) {
|
||||
fprintf(stderr, "player: subtitle file %s: can't be read\n", path.c_str());
|
||||
return false;
|
||||
}
|
||||
sub_check_count = player_subtitle_tracks().size();
|
||||
sub_check_at = plat_time() + 4;
|
||||
int r = libvlc_media_player_add_slave(mp, libvlc_media_slave_type_subtitle, file_uri(path).c_str(), true);
|
||||
int r = libvlc_media_player_add_slave(mp, libvlc_media_slave_type_subtitle, sub_uri(ready).c_str(), true);
|
||||
fprintf(stderr, "player: subtitle file %s: %s\n", path.c_str(), r == 0 ? "added" : "failed");
|
||||
if (r == 0 && std::find(kept.subs.begin(), kept.subs.end(), path) == kept.subs.end())
|
||||
kept.subs.push_back(path);
|
||||
/* the file VLC got is the one a reopen attaches again */
|
||||
if (r == 0 && std::find(kept.subs.begin(), kept.subs.end(), ready) == kept.subs.end())
|
||||
kept.subs.push_back(ready);
|
||||
/* VLC jumps to put the new track in step: not a "can't keep up" second */
|
||||
health.primed = false;
|
||||
health.next = plat_time() + 3;
|
||||
return r == 0;
|
||||
}
|
||||
|
||||
@@ -1616,10 +2122,11 @@ void video_draw(VkCommandBuffer cmd, float x, float y, float w, float h)
|
||||
p.view[2] = tanf(view_fov * 3.14159265f / 360);
|
||||
p.view[3] = h > 0 ? w / h : 16.0f / 9;
|
||||
}
|
||||
uint32_t vis_w = v.width && v.width <= planes.buf_w ? v.width : planes.buf_w;
|
||||
vis_h = v.height && v.height <= planes.buf_h ? v.height : planes.buf_h;
|
||||
p.uv_scale[0] = (float)vis_w / planes.buf_w;
|
||||
p.uv_scale[1] = (float)vis_h / planes.buf_h;
|
||||
/* The whole buffer is picture: vmem's pictures fill the size it set up
|
||||
* (1024x560 arrives stretched to 1024x578), so nothing is cut; the
|
||||
* rectangle has the picture's real shape. */
|
||||
p.uv_scale[0] = 1;
|
||||
p.uv_scale[1] = 1;
|
||||
VkViewport vp = { x, y, w, h, 0, 1 };
|
||||
/* The scissor stays on screen ("Fill" makes the rectangle larger than it). */
|
||||
float sx0 = x < 0 ? 0 : x, sy0 = y < 0 ? 0 : y;
|
||||
@@ -1667,12 +2174,36 @@ void player_set_subtitle_delay(int64_t ms)
|
||||
kept.spu_delay = ms;
|
||||
}
|
||||
|
||||
/* The title is a disc menu (a DVD's "DVD Menu" lists Resume, Root, Title...
|
||||
* as chapters: not chapters of anything). */
|
||||
static bool title_is_menu(int title)
|
||||
{
|
||||
libvlc_title_description_t **titles = nullptr;
|
||||
int n = libvlc_media_player_get_full_title_descriptions(mp, &titles);
|
||||
bool menu = n > 0 && title >= 0 && title < n && titles[title] && (titles[title]->i_flags & libvlc_title_menu);
|
||||
if (n > 0)
|
||||
libvlc_title_descriptions_release(titles, (unsigned)n);
|
||||
return menu;
|
||||
}
|
||||
|
||||
std::vector<Chapter> player_chapters()
|
||||
{
|
||||
if (!mp)
|
||||
return chapters_cache;
|
||||
/* A disc moves between titles (menu, intro, the film): each has its own
|
||||
* chapters, so the list is read again when the title changes. */
|
||||
int title = libvlc_media_player_get_title(mp);
|
||||
if (title != chapters_title) {
|
||||
chapters_cache.clear();
|
||||
chapters_tried = 0;
|
||||
chapters_title = title;
|
||||
}
|
||||
/* Known once the file is open; asked again at most once a second until then. */
|
||||
if (!mp || !chapters_cache.empty() || plat_time() < chapters_tried + 1)
|
||||
if (!chapters_cache.empty() || plat_time() < chapters_tried + 1)
|
||||
return chapters_cache;
|
||||
chapters_tried = plat_time();
|
||||
if (title_is_menu(title))
|
||||
return chapters_cache;
|
||||
libvlc_chapter_description_t **d = nullptr;
|
||||
int n = libvlc_media_player_get_full_chapter_descriptions(mp, -1, &d);
|
||||
for (int i = 0; i < n; i++) {
|
||||
@@ -1716,8 +2247,9 @@ bool player_screenshot(const std::string &path)
|
||||
shot.f = format;
|
||||
shot.data.assign(slots[last_slot].mapped, slots[last_slot].mapped + format.size);
|
||||
}
|
||||
shot.w = info.width && info.width <= shot.f.width ? info.width : shot.f.width;
|
||||
shot.h = info.height && info.height <= shot.f.height ? info.height : shot.f.height;
|
||||
/* the whole buffer: the picture fills it (see video_draw) */
|
||||
shot.w = shot.f.width;
|
||||
shot.h = shot.f.height;
|
||||
shot.path = path;
|
||||
std::thread(shot_main, std::move(shot)).detach();
|
||||
return true;
|
||||
@@ -1782,6 +2314,27 @@ void player_set_sub_style(const SubStyle &s)
|
||||
apply_sub_style(s);
|
||||
}
|
||||
|
||||
/* A, B or C: the saved choice, else the region of the interface's language
|
||||
* (A: the Americas, Japan, Korea, South-East Asia, Taiwan; B: Europe, the
|
||||
* Middle East, Africa, Oceania; C: China, Russia, India). */
|
||||
std::string player_bluray_region()
|
||||
{
|
||||
std::string r = pref_str("bd_region", "");
|
||||
if (r == "A" || r == "B" || r == "C")
|
||||
return r;
|
||||
std::string code = lang_code(lang_current());
|
||||
if (code == "zh-CN" || code == "ru")
|
||||
return "C";
|
||||
if (code == "en" || code == "es" || code == "pt-BR" || code == "ja" || code == "ko" || code == "zh-TW" || code == "id")
|
||||
return "A";
|
||||
return "B";
|
||||
}
|
||||
|
||||
void player_set_bluray_region(const std::string ®ion)
|
||||
{
|
||||
pref_set("bd_region", region);
|
||||
}
|
||||
|
||||
std::string player_audio_language()
|
||||
{
|
||||
return pref_str("lang_audio", "");
|
||||
|
||||
+13
-2
@@ -40,12 +40,20 @@ libvlc_instance_t *player_vlc();
|
||||
bool player_open(const std::string &path, int64_t start_ms, const std::vector<std::string> &options = {});
|
||||
/* The same with a media VLC made (an entry of a listed playlist, with the options
|
||||
* its list gave it); takes the reference. path: what it's called in the log. */
|
||||
bool player_open_media(libvlc_media_t *m, const std::string &path, int64_t start_ms);
|
||||
bool player_open_media(libvlc_media_t *m, const std::string &path, int64_t start_ms,
|
||||
const std::vector<std::string> &options = {});
|
||||
void player_stop();
|
||||
bool player_active(); /* a file is open (playing, paused, buffering) */
|
||||
bool player_ended(); /* the file played to its end (or failed) */
|
||||
bool player_failed();
|
||||
bool player_buffering(); /* opening or buffering: no picture moving yet */
|
||||
/* Disc menus: the main menu (DVD root, Blu-ray Top Menu) or a Blu-ray's
|
||||
* pop-up menu; whether the Blu-ray playing has one. */
|
||||
/* The Blu-ray region VLC plays as (A, B, C), from the next file. */
|
||||
std::string player_bluray_region();
|
||||
void player_set_bluray_region(const std::string ®ion);
|
||||
void player_disc_menu(bool popup);
|
||||
bool player_disc_has_popup();
|
||||
bool player_paused();
|
||||
void player_toggle_pause();
|
||||
int64_t player_time(); /* ms */
|
||||
@@ -62,7 +70,8 @@ std::vector<Track> player_subtitle_tracks();
|
||||
int player_subtitle_track();
|
||||
void player_set_subtitle_track(int id);
|
||||
/* Adds a subtitle file (.srt, .ass, ...) to what plays and selects it. */
|
||||
bool player_add_subtitle(const std::string &path);
|
||||
bool player_add_subtitle(const std::string &path); /* a local path, or a share's / a link's address */
|
||||
bool player_is_subtitle_name(const std::string &name); /* .srt, .ass, .vtt... */
|
||||
const VideoInfo &player_video_info();
|
||||
|
||||
/* A 360° video: where the view looks (degrees) and how wide it sees. */
|
||||
@@ -70,6 +79,8 @@ void player_view(float yaw, float pitch, float fov);
|
||||
|
||||
/* A DVD / Blu-ray menu: 0 activate, 1 up, 2 down, 3 left, 4 right. */
|
||||
void player_navigate(int action);
|
||||
/* A disc's menu is on screen (its current title is a menu). */
|
||||
bool player_in_menu();
|
||||
|
||||
/* The sound being heard, as n bands from bass to treble (0..1 each). */
|
||||
void player_spectrum(float *bands, int n);
|
||||
|
||||
+179
@@ -0,0 +1,179 @@
|
||||
/*
|
||||
* VLC-PS5: subtitle files that aren't UTF-8, made UTF-8 for VLC (see
|
||||
* subconv.h). Our own code; the conversion tables are GNU libiconv's.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
#include "subconv.h"
|
||||
|
||||
#include <iconv.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "platform.h"
|
||||
|
||||
namespace {
|
||||
|
||||
/* Valid UTF-8 (no overlongs, no surrogates, nothing past U+10FFFF). */
|
||||
bool is_utf8(const std::string &s)
|
||||
{
|
||||
const unsigned char *p = (const unsigned char *)s.data(), *end = p + s.size();
|
||||
while (p < end) {
|
||||
unsigned c = *p;
|
||||
if (c < 0x80) {
|
||||
p++;
|
||||
continue;
|
||||
}
|
||||
int n = c >= 0xF0 && c <= 0xF4 ? 3 : c >= 0xE0 ? 2 : c >= 0xC2 && c <= 0xDF ? 1 : -1;
|
||||
if (n < 0 || c > 0xF4 || end - p <= n)
|
||||
return false;
|
||||
uint32_t cp = c & (0x3F >> n);
|
||||
for (int i = 1; i <= n; i++) {
|
||||
if ((p[i] & 0xC0) != 0x80)
|
||||
return false;
|
||||
cp = cp << 6 | (p[i] & 0x3F);
|
||||
}
|
||||
if ((n == 2 && cp < 0x800) || (n == 3 && (cp < 0x10000 || cp > 0x10FFFF)) || (cp >= 0xD800 && cp <= 0xDFFF))
|
||||
return false;
|
||||
p += n + 1;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* The code page subtitles in that language are usually saved in when they
|
||||
* aren't Unicode (what Windows' "ANSI" was for it). */
|
||||
const char *legacy_encoding(const std::string &lang)
|
||||
{
|
||||
std::string l = lang.substr(0, lang.find('-'));
|
||||
if (lang == "zh-TW" || lang == "zh-HK" || lang == "zht")
|
||||
return "BIG5-HKSCS";
|
||||
if (l == "zh" || l == "chi" || l == "zho")
|
||||
return "GB18030";
|
||||
if (l == "ja" || l == "jpn")
|
||||
return "CP932";
|
||||
if (l == "ko" || l == "kor")
|
||||
return "CP949";
|
||||
if (l == "ru" || l == "rus" || l == "uk" || l == "ukr" || l == "bg" || l == "bul" || l == "sr" || l == "mk" || l == "be")
|
||||
return "CP1251";
|
||||
if (l == "ar" || l == "ara" || l == "fa" || l == "per" || l == "fas" || l == "ur")
|
||||
return "CP1256";
|
||||
if (l == "he" || l == "heb")
|
||||
return "CP1255";
|
||||
if (l == "tr" || l == "tur")
|
||||
return "CP1254";
|
||||
if (l == "el" || l == "gre" || l == "ell")
|
||||
return "CP1253";
|
||||
if (l == "pl" || l == "pol" || l == "cs" || l == "cze" || l == "ces" || l == "sk" || l == "hu" || l == "hun" ||
|
||||
l == "ro" || l == "rum" || l == "hr" || l == "sl")
|
||||
return "CP1250";
|
||||
if (l == "th" || l == "tha")
|
||||
return "CP874";
|
||||
if (l == "vi" || l == "vie")
|
||||
return "CP1258";
|
||||
return "CP1252";
|
||||
}
|
||||
|
||||
bool convert(const std::string &in, const char *from, std::string *out)
|
||||
{
|
||||
iconv_t cd = iconv_open("UTF-8", from);
|
||||
if (cd == (iconv_t)-1)
|
||||
return false;
|
||||
out->clear();
|
||||
out->resize(in.size() * 4 + 16);
|
||||
char *src = (char *)in.data(), *dst = &(*out)[0];
|
||||
size_t left = in.size(), room = out->size();
|
||||
bool ok = true;
|
||||
while (left > 0) {
|
||||
if (iconv(cd, &src, &left, &dst, &room) == (size_t)-1) {
|
||||
/* a byte the code page hasn't got: U+FFFD, and on */
|
||||
if (room < 3) {
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
memcpy(dst, "\xEF\xBF\xBD", 3);
|
||||
dst += 3;
|
||||
room -= 3;
|
||||
src++;
|
||||
left--;
|
||||
}
|
||||
}
|
||||
iconv_close(cd);
|
||||
out->resize(out->size() - room);
|
||||
return ok;
|
||||
}
|
||||
|
||||
std::string copy_dir()
|
||||
{
|
||||
std::string d = std::string(plat_data_dir()) + "/cache";
|
||||
mkdir(d.c_str(), 0777);
|
||||
d += "/subtitles";
|
||||
mkdir(d.c_str(), 0777);
|
||||
d += "/converted";
|
||||
mkdir(d.c_str(), 0777);
|
||||
return d;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::string subconv_to_utf8(const std::string &bytes, const std::string &lang, std::string *from)
|
||||
{
|
||||
if (from)
|
||||
from->clear();
|
||||
const unsigned char *b = (const unsigned char *)bytes.data();
|
||||
std::string out;
|
||||
if (bytes.size() >= 2 && ((b[0] == 0xFF && b[1] == 0xFE) || (b[0] == 0xFE && b[1] == 0xFF))) {
|
||||
const char *enc = b[0] == 0xFF ? "UTF-16LE" : "UTF-16BE";
|
||||
if (convert(bytes.substr(2), enc, &out)) {
|
||||
if (from)
|
||||
*from = enc;
|
||||
return out;
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
if (is_utf8(bytes))
|
||||
return bytes;
|
||||
const char *enc = legacy_encoding(lang);
|
||||
if (!convert(bytes, enc, &out))
|
||||
return bytes;
|
||||
if (from)
|
||||
*from = enc;
|
||||
return out;
|
||||
}
|
||||
|
||||
std::string subconv_bytes(const std::string &bytes, const std::string &name, const std::string &lang)
|
||||
{
|
||||
std::string from;
|
||||
std::string utf8 = subconv_to_utf8(bytes, lang, &from);
|
||||
if (from.empty())
|
||||
return "";
|
||||
/* The name kept (VLC shows it, and finds the format by its extension). */
|
||||
std::string base = name.substr(name.rfind('/') + 1);
|
||||
std::string path = copy_dir() + "/" + base;
|
||||
FILE *f = fopen(path.c_str(), "wb");
|
||||
if (!f)
|
||||
return "";
|
||||
fwrite(utf8.data(), 1, utf8.size(), f);
|
||||
fclose(f);
|
||||
fprintf(stderr, "subconv: %s read as %s, UTF-8 copy %s\n", base.c_str(), from.c_str(), path.c_str());
|
||||
return path;
|
||||
}
|
||||
|
||||
std::string subconv_local(const std::string &path, const std::string &lang)
|
||||
{
|
||||
FILE *f = fopen(path.c_str(), "rb");
|
||||
if (!f)
|
||||
return path;
|
||||
std::string bytes;
|
||||
char buf[65536];
|
||||
size_t n;
|
||||
/* subtitle files are small: a big one is something else, left alone */
|
||||
while ((n = fread(buf, 1, sizeof(buf), f)) > 0 && bytes.size() < (16u << 20))
|
||||
bytes.append(buf, n);
|
||||
fclose(f);
|
||||
if (bytes.size() >= (16u << 20))
|
||||
return path;
|
||||
std::string copy = subconv_bytes(bytes, path, lang);
|
||||
return copy.empty() ? path : copy;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* VLC-PS5: subtitle files that aren't UTF-8 (Windows-1252, GBK, Big5, Shift
|
||||
* JIS, Windows-1256...). The console's VLC has no iconv, so it read them as
|
||||
* UTF-8 and showed mangled letters; such a file is converted here (GNU
|
||||
* libiconv) to a UTF-8 copy in cache/subtitles/converted, which VLC gets
|
||||
* instead.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
/* The bytes of a subtitle file made UTF-8: as they are when they already are
|
||||
* (or the encoding can't be told), else converted from UTF-16 (by its BOM)
|
||||
* or from the legacy code page of lang ("zh-CN", "ar", "ru"...; "" = Western).
|
||||
* *from says which encoding it was read as ("" = left alone). */
|
||||
std::string subconv_to_utf8(const std::string &bytes, const std::string &lang, std::string *from = nullptr);
|
||||
|
||||
/* A local subtitle file ready for VLC: the path itself when it's UTF-8 (or
|
||||
* can't be read), else the path of a converted UTF-8 copy. */
|
||||
std::string subconv_local(const std::string &path, const std::string &lang);
|
||||
|
||||
/* The same for bytes already read (a file on a share or a link): the copy's
|
||||
* path, or "" when they're UTF-8 already and VLC can read the original. name:
|
||||
* the file's name, for the copy's. */
|
||||
std::string subconv_bytes(const std::string &bytes, const std::string &name, const std::string &lang);
|
||||
@@ -171,6 +171,9 @@ std::string vlc_message(const std::string &m)
|
||||
}
|
||||
if (m.compare(0, 14, "Please provide") == 0)
|
||||
return "A Windows PC takes its own account; a NAS, the user you made on it.";
|
||||
/* libbluray without a JVM (none runs on the console): not "install Java" */
|
||||
if (m.compare(0, 30, "This Blu-ray disc requires Java") == 0)
|
||||
return "This Blu-ray's menus are made in Java, which the PS5 can't run: it plays without them";
|
||||
if (m.compare(0, 6, "Track ") == 0 && m.size() > 6 && isdigit((unsigned char)m[6]))
|
||||
return trf("Track %d", atoi(m.c_str() + 6));
|
||||
return m;
|
||||
@@ -1042,14 +1045,73 @@ bool audio_name(const std::string &name)
|
||||
return false;
|
||||
}
|
||||
|
||||
/* A file on a share by its name, as ci_file() kinds: 0 video, 1 audio,
|
||||
* 3 text, 4 image, 5 another file (subtitles, databases...). */
|
||||
int name_kind(const std::string &name)
|
||||
{
|
||||
if (audio_name(name))
|
||||
return 1;
|
||||
std::string ext = name.substr(name.rfind('.') + 1);
|
||||
for (char &c : ext)
|
||||
c = (char)tolower((unsigned char)c);
|
||||
for (const char *x : { "jpg", "jpeg", "png", "gif", "bmp", "webp", "tif", "tiff" })
|
||||
if (ext == x)
|
||||
return 4;
|
||||
for (const char *x : { "txt", "nfo", "diz", "log", "md" })
|
||||
if (ext == x)
|
||||
return 3;
|
||||
for (const char *x : { "srt", "ass", "ssa", "sub", "idx", "vtt", "sup", "db", "ini", "sfv", "cue", "exe", "pdf" })
|
||||
if (ext == x)
|
||||
return 5;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* A list of links (IPTV channels, radio), not a stream: .m3u and .pls, and
|
||||
* .m3u8 unless it's online (an online .m3u8 is usually a live HLS stream). */
|
||||
bool is_playlist_url(const std::string &url)
|
||||
{
|
||||
std::string u = url.substr(0, url.find('?'));
|
||||
size_t dot = u.rfind('.');
|
||||
if (dot == std::string::npos || u.find('/', dot) != std::string::npos)
|
||||
return false;
|
||||
const char *x = u.c_str() + dot + 1;
|
||||
if (!strcasecmp(x, "m3u8"))
|
||||
return u.compare(0, 4, "http") != 0;
|
||||
return !strcasecmp(x, "m3u") || !strcasecmp(x, "pls");
|
||||
}
|
||||
|
||||
/* file:///abs/path with everything but plain characters escaped. */
|
||||
std::string file_uri(std::string path)
|
||||
{
|
||||
std::string uri = "file://";
|
||||
if (path[0] != '/') { /* the test build's data folder is relative */
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof(cwd)))
|
||||
path = std::string(cwd) + "/" + path;
|
||||
}
|
||||
for (unsigned char c : path) {
|
||||
if (isalnum(c) || strchr("/-_.~", c)) {
|
||||
uri += (char)c;
|
||||
} else {
|
||||
char hex[4];
|
||||
snprintf(hex, sizeof(hex), "%%%02X", c);
|
||||
uri += hex;
|
||||
}
|
||||
}
|
||||
return uri;
|
||||
}
|
||||
|
||||
void rebuild_browse()
|
||||
{
|
||||
browse_entries.clear();
|
||||
auto &items = L();
|
||||
if (!net_path.empty()) {
|
||||
if (net_list_state() == NET_READY)
|
||||
for (const NetEntry &e : net_list_entries())
|
||||
browse_entries.push_back({ e.name, e.url, e.dir, -1, false, e.dir ? NET_DIR : NET_FILE });
|
||||
for (const NetEntry &e : net_list_entries()) {
|
||||
/* a channel list on a share opens like a folder */
|
||||
bool list = !e.dir && is_playlist_url(e.url);
|
||||
browse_entries.push_back({ e.name, e.url, e.dir, -1, list, e.dir || list ? NET_DIR : NET_FILE });
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (browse_dir.empty()) {
|
||||
@@ -1252,9 +1314,26 @@ int current_item()
|
||||
return m ? (int)(m - &L()[0]) : -1;
|
||||
}
|
||||
|
||||
void net_enter(const std::string &url, const std::string &name);
|
||||
|
||||
/* A playlist with links (IPTV channels, radio): its entries in Browse, where
|
||||
* each one plays as a stream. */
|
||||
void open_channel_list(const std::string &path)
|
||||
{
|
||||
std::string name = path.substr(path.rfind('/') + 1);
|
||||
name = name.substr(0, name.rfind('.'));
|
||||
screen = LIBRARY;
|
||||
tab = BROWSE;
|
||||
net_enter(file_uri(path), name);
|
||||
}
|
||||
|
||||
/* A playlist file: its files, from the first (or a random one with shuffle). */
|
||||
void play_playlist(const std::string &path)
|
||||
{
|
||||
if (library_playlist_has_links(path)) {
|
||||
open_channel_list(path);
|
||||
return;
|
||||
}
|
||||
std::vector<int> list = library_playlist_items(path);
|
||||
if (list.empty()) {
|
||||
toast("No files of this playlist are in the library", 3);
|
||||
@@ -2032,6 +2111,23 @@ const std::vector<int> &playlist_items_cached(const std::string &path)
|
||||
return c.items;
|
||||
}
|
||||
|
||||
/* How many entries a playlist names: its library files, or every line of a
|
||||
* channel list (links aren't library files). */
|
||||
int playlist_count_cached(const std::string &path)
|
||||
{
|
||||
struct Cached {
|
||||
double at;
|
||||
int n;
|
||||
};
|
||||
static std::unordered_map<std::string, Cached> cache;
|
||||
Cached &c = cache[path];
|
||||
if (c.at == 0 || now - c.at > 3) {
|
||||
c.n = library_playlist_has_links(path) ? library_playlist_count(path) : (int)playlist_items_cached(path).size();
|
||||
c.at = now;
|
||||
}
|
||||
return c.n;
|
||||
}
|
||||
|
||||
/* A picture covering (x, y, w, h): the thumbnail cropped to that shape. */
|
||||
void thumb_cover(const MediaItem &m, float x, float y, float w, float h, float round, ImDrawFlags corners)
|
||||
{
|
||||
@@ -2118,8 +2214,7 @@ void playlist_card(float x, float y, float w, const PlaylistFile *pl, float f)
|
||||
if (pl) {
|
||||
playlist_cover(pl->path, cx, cy, cw, ch, 16);
|
||||
/* how many, bottom right, like a video's length */
|
||||
const std::vector<int> &items = playlist_items_cached(pl->path);
|
||||
std::string count = std::to_string(items.size());
|
||||
std::string count = std::to_string(playlist_count_cached(pl->path));
|
||||
float tw = text_size(f_semi, 18, count.c_str()).x;
|
||||
rect(cx + cw - tw - 54, cy + ch - 40, tw + 44, 28, IM_COL32(0, 0, 0, 170), 8);
|
||||
icon_list(cx + cw - tw - 38, cy + ch - 26, 16, C_TEXT);
|
||||
@@ -2164,6 +2259,8 @@ void tab_playlists()
|
||||
if (pl_focus == 0) {
|
||||
pl_add_file.clear();
|
||||
open_playlist_name("");
|
||||
} else if (library_playlist_has_links(pls[pl_focus - 1].path)) {
|
||||
open_channel_list(pls[pl_focus - 1].path);
|
||||
} else {
|
||||
pl_open = pls[pl_focus - 1].path;
|
||||
pl_item_focus = 0;
|
||||
@@ -2394,26 +2491,11 @@ void net_enter(const std::string &url, const std::string &name);
|
||||
|
||||
void open_archive(const MediaItem &m)
|
||||
{
|
||||
std::string uri = "file://", path = m.path;
|
||||
if (path[0] != '/') { /* the test build's data folder is relative */
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof(cwd)))
|
||||
path = std::string(cwd) + "/" + path;
|
||||
}
|
||||
for (unsigned char c : path) {
|
||||
if (isalnum(c) || strchr("/-_.~", c)) {
|
||||
uri += (char)c;
|
||||
} else {
|
||||
char hex[4];
|
||||
snprintf(hex, sizeof(hex), "%%%02X", c);
|
||||
uri += hex;
|
||||
}
|
||||
}
|
||||
tab = BROWSE;
|
||||
std::string ext = m.ext;
|
||||
for (char &c : ext)
|
||||
c = (char)tolower((unsigned char)c);
|
||||
net_enter(uri, m.name + "." + ext);
|
||||
net_enter(file_uri(m.path), m.name + "." + ext);
|
||||
}
|
||||
|
||||
/* Opens a network place in Browse (a server, or a folder on it). */
|
||||
@@ -2426,6 +2508,80 @@ void net_enter(const std::string &url, const std::string &name)
|
||||
rebuild_browse();
|
||||
}
|
||||
|
||||
/* ---- picking a subtitle file for the video playing (Tracks › Add a
|
||||
* subtitle file): Browse, from anywhere it reaches, then back to the video */
|
||||
std::string link_name(const std::string &url);
|
||||
bool sub_pick;
|
||||
bool sub_pick_resume; /* it was playing: it plays on after */
|
||||
std::string sub_pick_dir;
|
||||
std::vector<std::pair<std::string, std::string>> sub_pick_net;
|
||||
|
||||
void start_sub_pick()
|
||||
{
|
||||
pressed = 0; /* the ✕ that asked is used up: the player mustn't pause on it too */
|
||||
sub_pick = true;
|
||||
sub_pick_resume = !player_paused();
|
||||
if (sub_pick_resume)
|
||||
player_toggle_pause();
|
||||
fprintf(stderr, "ui: picking a subtitle file (%s)\n", sub_pick_resume ? "paused the video" : "the video was paused");
|
||||
/* what Browse showed before comes back after */
|
||||
sub_pick_dir = browse_dir;
|
||||
sub_pick_net = net_path;
|
||||
modal = NONE;
|
||||
screen = LIBRARY;
|
||||
screen_fade = 0;
|
||||
tab = BROWSE;
|
||||
library_set_busy(false);
|
||||
/* It starts at the top (Sources): the media folder, drives and shares
|
||||
* alike, whatever the video came from; a subtitle can be anywhere. */
|
||||
net_path.clear();
|
||||
browse_dir.clear();
|
||||
net_list_stop();
|
||||
rebuild_browse();
|
||||
browse_focus = 0;
|
||||
browse_scroll = 0;
|
||||
}
|
||||
|
||||
void end_sub_pick(std::string picked) /* a copy: it may be a Browse entry, and Browse is rebuilt below */
|
||||
{
|
||||
pressed = 0;
|
||||
sub_pick = false;
|
||||
net_path = sub_pick_net;
|
||||
browse_dir = sub_pick_dir;
|
||||
if (!net_path.empty())
|
||||
net_list(net_path.back().first);
|
||||
else
|
||||
net_list_stop();
|
||||
rebuild_browse();
|
||||
browse_focus = 0;
|
||||
screen = PLAYER;
|
||||
screen_fade = 0;
|
||||
library_set_busy(true);
|
||||
osd_until = now + 3.5;
|
||||
/* playing again first: a subtitle added to a paused file left VLC paused
|
||||
* after the resume */
|
||||
if (sub_pick_resume && player_paused())
|
||||
player_toggle_pause();
|
||||
if (!picked.empty()) {
|
||||
std::string name = picked.find("://") != std::string::npos ? link_name(picked)
|
||||
: picked.substr(picked.rfind('/') + 1);
|
||||
if (player_add_subtitle(picked))
|
||||
toast(trf("Subtitles: %s", name.c_str()), 2.5);
|
||||
else
|
||||
toast("Couldn't load that subtitle file", 2.5);
|
||||
}
|
||||
fprintf(stderr, "ui: subtitle pick done (%s)\n", picked.empty() ? "none" : "picked");
|
||||
}
|
||||
|
||||
/* ✕ on a file while picking: a subtitle file goes in, anything else says so. */
|
||||
void sub_pick_file(std::string path, std::string name) /* copies, as above */
|
||||
{
|
||||
if (player_is_subtitle_name(name))
|
||||
end_sub_pick(path);
|
||||
else
|
||||
toast("That isn't a subtitle file (.srt, .ass, .vtt...)", 2.5);
|
||||
}
|
||||
|
||||
/* "1.4 GB" */
|
||||
std::string size_text(int64_t bytes)
|
||||
{
|
||||
@@ -2454,7 +2610,9 @@ void classic_label(const BrowseEntry &e, std::string *name, std::string *type, i
|
||||
*type = e.path == "tv:" ? "Channel lists (iptv-org)" : "Stations (radio-browser.info)";
|
||||
*group = 2;
|
||||
} else if (e.net == NET_DIR) {
|
||||
*type = "File folder";
|
||||
*type = e.playlist ? "Playlist" : "File folder";
|
||||
if (e.playlist && e.name.rfind('.') != std::string::npos && e.name.rfind('.') > 0)
|
||||
*name = e.name.substr(0, e.name.rfind('.')); /* like local playlists */
|
||||
} else if (e.net == NET_FILE) {
|
||||
size_t dot = e.name.rfind('.');
|
||||
bool has_ext = dot != std::string::npos && dot > 0 && e.name.size() - dot <= 6;
|
||||
@@ -2464,7 +2622,10 @@ void classic_label(const BrowseEntry &e, std::string *name, std::string *type, i
|
||||
std::string ext = e.name.substr(dot + 1);
|
||||
for (char &c : ext)
|
||||
c = (char)toupper((unsigned char)c);
|
||||
*type = ext + (audio_name(e.name) ? " audio" : " video");
|
||||
int k = name_kind(e.name);
|
||||
*type = k == 3 ? std::string(tr("Text document"))
|
||||
: k == 5 ? trf("%s file", ext.c_str())
|
||||
: trf(k == 4 ? "%s image" : k == 1 ? "%s audio" : "%s video", ext.c_str());
|
||||
} else {
|
||||
*type = "Live stream";
|
||||
}
|
||||
@@ -2490,7 +2651,7 @@ void classic_label(const BrowseEntry &e, std::string *name, std::string *type, i
|
||||
*type = "File folder";
|
||||
} else if (e.item >= 0 && L()[e.item].disc) {
|
||||
const MediaItem &m = L()[e.item];
|
||||
*type = m.ext == "ISO" ? "Disc image (ISO)" : m.ext == "DVD" ? "DVD (folder)" : "Blu-ray (folder)";
|
||||
*type = m.ext == "ISO" || m.ext == "IMG" ? "Disc image (ISO)" : m.ext == "DVD" ? "DVD (folder)" : "Blu-ray (folder)";
|
||||
} else if (e.item >= 0 && L()[e.item].archive) {
|
||||
*type = trf("Compressed (%s)", L()[e.item].ext.c_str());
|
||||
} else if (e.item >= 0 && L()[e.item].other) {
|
||||
@@ -2511,7 +2672,7 @@ void classic_icon(const BrowseEntry &e, float cx, float cy, float s)
|
||||
else if (e.net == NET_ONLINE)
|
||||
e.path == "tv:" ? ci_tv(cx, cy, s) : ci_radio(cx, cy, s);
|
||||
else if (e.net == NET_FILE)
|
||||
ci_file(cx, cy, s, audio_name(e.name) ? 1 : 0);
|
||||
ci_file(cx, cy, s, name_kind(e.name));
|
||||
else if (e.dir && browse_dir.empty() && net_path.empty() && e.net == NET_NONE)
|
||||
e.path == library_media_dir() ? ci_folder(cx, cy, s) : ci_drive(cx, cy, s, e.path.find("/usb") != std::string::npos);
|
||||
else if (e.dir)
|
||||
@@ -2548,7 +2709,7 @@ void browse_draw_classic()
|
||||
tri(106, ay + ah / 2, 118, ay + ah / 2 - 10, 118, ay + ah / 2 + 10, arrow);
|
||||
rect(117, ay + ah / 2 - 3.5f, 13, 7, arrow);
|
||||
/* the path, in segments */
|
||||
std::vector<std::string> segs = { "Sources" };
|
||||
std::vector<std::string> segs = { sub_pick ? "Pick a subtitle file" : "Sources" };
|
||||
/* an archive opened from a folder: the folder's path, then inside it */
|
||||
bool in_archive = !net_path.empty() && net_path[0].first.compare(0, 7, "file://") == 0;
|
||||
if (!net_path.empty() && !in_archive)
|
||||
@@ -2803,6 +2964,14 @@ void tab_browse()
|
||||
toast(std::string(1, letter_of(names[to])), 0.8);
|
||||
}
|
||||
}
|
||||
if (sub_pick && hit(PAD_CIRCLE) && net_path.empty() && browse_dir.empty()) {
|
||||
end_sub_pick(""); /* ○ at the top: back to the video, nothing picked */
|
||||
return;
|
||||
}
|
||||
if (sub_pick && hit(PAD_CROSS) && n && browse_entries[browse_focus].net == NET_FILE) {
|
||||
sub_pick_file(browse_entries[browse_focus].path, browse_entries[browse_focus].name);
|
||||
return;
|
||||
}
|
||||
if (hit(PAD_CROSS) && n && browse_entries[browse_focus].net != NET_NONE) {
|
||||
BrowseEntry e = browse_entries[browse_focus];
|
||||
if (e.net == NET_ADD) {
|
||||
@@ -2840,8 +3009,14 @@ void tab_browse()
|
||||
rebuild_browse();
|
||||
return;
|
||||
}
|
||||
if (sub_pick) {
|
||||
std::string path = e.item >= 0 ? L()[e.item].path : e.path;
|
||||
sub_pick_file(path, path.substr(path.rfind('/') + 1));
|
||||
return;
|
||||
}
|
||||
if (e.playlist) {
|
||||
play_playlist(e.path);
|
||||
/* its entries, like a folder: pick a channel or a song */
|
||||
net_enter(file_uri(e.path), e.name);
|
||||
return;
|
||||
}
|
||||
std::vector<int> list;
|
||||
@@ -2870,7 +3045,7 @@ void tab_browse()
|
||||
return;
|
||||
}
|
||||
/* Breadcrumb: "Media › Movies", not the raw path. */
|
||||
std::string crumb = tr("Sources");
|
||||
std::string crumb = tr(sub_pick ? "Pick a subtitle file" : "Sources");
|
||||
bool in_archive = !net_path.empty() && net_path[0].first.compare(0, 7, "file://") == 0;
|
||||
if (!net_path.empty() && !in_archive)
|
||||
crumb = tr("Network");
|
||||
@@ -2909,8 +3084,17 @@ void tab_browse()
|
||||
icon_radio(146, y + 33, 34, mix(C_DIM, C_ORANGE, f));
|
||||
else if (e.net == NET_ADD)
|
||||
icon_plus_circle(146, y + 33, 32, mix(C_DIM, C_ORANGE, f));
|
||||
else if (e.net == NET_FILE)
|
||||
audio_name(e.name) ? icon_note(146, y + 33, 28, C_DIM) : icon_film(146, y + 33, 34, C_DIM);
|
||||
else if (e.net == NET_FILE) {
|
||||
int k = name_kind(e.name);
|
||||
if (k == 1)
|
||||
icon_note(146, y + 33, 28, C_DIM);
|
||||
else if (k == 4)
|
||||
icon_picture(146, y + 33, 34, C_DIM);
|
||||
else if (k == 3 || k == 5)
|
||||
icon_list(146, y + 33, 30, k == 5 ? C_FAINT : C_DIM);
|
||||
else
|
||||
icon_film(146, y + 33, 34, C_DIM);
|
||||
}
|
||||
else if (e.dir)
|
||||
icon_folder(146, y + 33, 34, mix(C_DIM, C_ORANGE, f));
|
||||
else if (e.playlist)
|
||||
@@ -3389,8 +3573,25 @@ std::string stream_name;
|
||||
void start_stream(const std::string &url, const std::vector<std::string> &options, bool remember,
|
||||
const std::string &name, libvlc_media_t *media)
|
||||
{
|
||||
if (is_playlist_url(url)) {
|
||||
/* A channel list (typed, or a file on a share): its entries in Browse. */
|
||||
if (remember)
|
||||
remember_link(url);
|
||||
screen = LIBRARY;
|
||||
tab = BROWSE;
|
||||
net_enter(url, name.empty() ? link_name(url) : name);
|
||||
return;
|
||||
}
|
||||
music_bg = false;
|
||||
stream_options = options;
|
||||
std::vector<std::string> opts = options;
|
||||
std::string bare = url.substr(0, url.find('?'));
|
||||
if (bare.size() > 4 && (!strcasecmp(bare.c_str() + bare.size() - 4, ".iso") || !strcasecmp(bare.c_str() + bare.size() - 4, ".img")) && url.compare(0, 7, "file://") != 0 &&
|
||||
std::find(opts.begin(), opts.end(), ":demux=dvd,any") == opts.end())
|
||||
/* A disc image on a share or a link: VLC's DVD reader only tries it by
|
||||
* itself on fast-seeking sources (not SMB), so ask for it first; a
|
||||
* Blu-ray image falls through to the Blu-ray reader ("any"). */
|
||||
opts.push_back(":demux=dvd,any");
|
||||
stream_options = opts;
|
||||
stream_remember = remember;
|
||||
stream_name = name;
|
||||
playing_path = url;
|
||||
@@ -3402,11 +3603,11 @@ void start_stream(const std::string &url, const std::vector<std::string> &option
|
||||
library_set_busy(true);
|
||||
bool opened;
|
||||
if (media) {
|
||||
for (const std::string &o : options)
|
||||
for (const std::string &o : opts)
|
||||
libvlc_media_add_option(media, o.c_str());
|
||||
opened = player_open_media(media, url, 0);
|
||||
opened = player_open_media(media, url, 0, opts);
|
||||
} else {
|
||||
opened = player_open(url, 0, options);
|
||||
opened = player_open(url, 0, opts);
|
||||
}
|
||||
if (!opened) {
|
||||
toast("Couldn't open that link", 3);
|
||||
@@ -4558,13 +4759,13 @@ void setup_begin(bool with_logo);
|
||||
enum {
|
||||
ST_DECODING, ST_ALANG, ST_SLANG, ST_SOUND, ST_NIGHT, ST_SPEAKERS, ST_THEME, ST_HAPTICS,
|
||||
ST_SWIPES, ST_REPEAT, ST_SHUFFLE, ST_DEBUG, ST_STORAGE, ST_VERSION, ST_RELOAD, ST_ABOUT,
|
||||
ST_WEB, ST_LOOK, ST_OSUB, ST_UILANG, ST_SETUP, ST_COUNT
|
||||
ST_WEB, ST_LOOK, ST_OSUB, ST_UILANG, ST_SETUP, ST_BDREGION, ST_COUNT
|
||||
};
|
||||
const char *const st_labels[ST_COUNT] = {
|
||||
"Decoding", "Audio language", "Subtitle language", "Sound output", "Night mode",
|
||||
"Speaker test", "Theme", "Controller vibration", "Touchpad swipes", "Repeat the list", "Shuffle",
|
||||
"Detailed log", "Storage", "Version", "Reload drives", "About VLC", "Phone access", "Look",
|
||||
"Subtitle downloads", "Language", "Run setup again",
|
||||
"Subtitle downloads", "Language", "Run setup again", "Blu-ray region",
|
||||
};
|
||||
/* One line under the focused setting: what it does. */
|
||||
const char *const st_help[ST_COUNT] = {
|
||||
@@ -4589,6 +4790,7 @@ const char *const st_help[ST_COUNT] = {
|
||||
"Your own free OpenSubtitles API key (and account), here or from the phone page",
|
||||
"The language of VLC's menus",
|
||||
"Language, look and playback, chosen again",
|
||||
"A: the Americas and East Asia. B: Europe, Africa and Oceania. C: China, Russia and India",
|
||||
};
|
||||
|
||||
/* Categories: an icon, a name, and their settings. */
|
||||
@@ -4599,7 +4801,7 @@ struct SettingsCat {
|
||||
};
|
||||
enum { SI_PLAY, SI_SOUND, SI_THEME, SI_LIST, SI_GEAR };
|
||||
const SettingsCat settings_cats[] = {
|
||||
{ "Playback", SI_PLAY, { ST_DECODING, ST_ALANG, ST_SLANG, ST_OSUB } },
|
||||
{ "Playback", SI_PLAY, { ST_DECODING, ST_ALANG, ST_SLANG, ST_OSUB, ST_BDREGION } },
|
||||
{ "Sound", SI_SOUND, { ST_SOUND, ST_NIGHT, ST_SPEAKERS } },
|
||||
{ "Interface", SI_THEME, { ST_UILANG, ST_LOOK, ST_THEME, ST_SWIPES } },
|
||||
{ "Playlists", SI_LIST, { ST_REPEAT, ST_SHUFFLE } },
|
||||
@@ -4724,6 +4926,7 @@ void setting_icon(int id, float cx, float cy, float s, ImU32 col)
|
||||
case ST_DECODING: si_chip(cx, cy, s, col); break;
|
||||
case ST_ALANG: case ST_UILANG: si_globe(cx, cy, s, col); break;
|
||||
case ST_SLANG: case ST_OSUB: icon_cc(cx, cy, s * 0.9f, col); break;
|
||||
case ST_BDREGION: si_globe(cx, cy, s, col); break;
|
||||
case ST_SOUND: icon_speaker(cx, cy, s * 0.85f, col); break;
|
||||
case ST_NIGHT: si_moon(cx, cy, s, col); break;
|
||||
case ST_SPEAKERS: si_surround(cx, cy, s, col); break;
|
||||
@@ -4794,6 +4997,7 @@ std::string st_value(int id)
|
||||
}
|
||||
case ST_WEB: return web_running() ? "On" : "Off";
|
||||
case ST_ABOUT: return "v" VLC_PS5_VERSION;
|
||||
case ST_BDREGION: return trf("Region %s", player_bluray_region().c_str());
|
||||
default: return "";
|
||||
}
|
||||
}
|
||||
@@ -4807,6 +5011,14 @@ void st_change(int id, int step)
|
||||
break;
|
||||
case ST_ALANG: cycle_language(false, dir); break;
|
||||
case ST_SLANG: cycle_language(true, dir); break;
|
||||
case ST_BDREGION: {
|
||||
static const char *const regions[3] = { "A", "B", "C" };
|
||||
std::string r = player_bluray_region();
|
||||
int i = r == "B" ? 1 : r == "C" ? 2 : 0;
|
||||
player_set_bluray_region(regions[(i + 3 + dir) % 3]);
|
||||
toast("Blu-ray region: from the next disc you open", 2.5);
|
||||
break;
|
||||
}
|
||||
case ST_SOUND:
|
||||
player_set_audio_output(!player_audio_output());
|
||||
toast(player_audio_output() ? "Surround: 5.1 / 7.1 files from the next one you open" : "Stereo from the next file", 3);
|
||||
@@ -5414,12 +5626,24 @@ struct PanelEntry {
|
||||
std::string shown; /* kind 7: the value, right-aligned */
|
||||
};
|
||||
|
||||
/* A disc plays: from the library, dvd:// / bluray://, or an .iso / .img
|
||||
* from a share or a link. */
|
||||
bool playing_disc()
|
||||
{
|
||||
int cur_item = current_item();
|
||||
std::string bare = playing_path.substr(0, playing_path.find('?'));
|
||||
return (cur_item >= 0 && L()[cur_item].disc) || playing_path.compare(0, 6, "dvd://") == 0 ||
|
||||
playing_path.compare(0, 9, "bluray://") == 0 ||
|
||||
(bare.size() > 4 && (!strcasecmp(bare.c_str() + bare.size() - 4, ".iso") || !strcasecmp(bare.c_str() + bare.size() - 4, ".img")));
|
||||
}
|
||||
|
||||
/* The settings rows (PanelEntry kind 7). */
|
||||
enum {
|
||||
SET_AUDIO_DELAY, SET_EQ, SET_SUB_DELAY, SET_SUB_SIZE, SET_SUB_COLOUR, SET_SUB_BOX, SET_SLEEP,
|
||||
SET_LOOP, SET_DEINT, SET_BRIGHT, SET_CONTRAST, SET_SAT, SET_GAMMA, SET_HUE, SET_ROTATE, SET_FLIP,
|
||||
SET_SHARPEN, SET_PIC_RESET,
|
||||
SET_SOUND_OUT, SET_NIGHT, SET_REPEAT, SET_SHUFFLE, SET_BOOKMARK, SET_BM_CLEAR, SET_OSUB,
|
||||
SET_DISC_MENU, SET_POPUP, SET_SUB_PICK,
|
||||
};
|
||||
|
||||
std::string ms_text(int64_t ms)
|
||||
@@ -5503,6 +5727,18 @@ void setting_change(int id, int step)
|
||||
if (step == 0)
|
||||
open_osub_results();
|
||||
return;
|
||||
case SET_SUB_PICK:
|
||||
if (step == 0)
|
||||
start_sub_pick();
|
||||
return;
|
||||
case SET_DISC_MENU:
|
||||
case SET_POPUP:
|
||||
/* the menu shows over the film: the panel gets out of its way */
|
||||
if (step == 0) {
|
||||
player_disc_menu(id == SET_POPUP);
|
||||
modal = NONE;
|
||||
}
|
||||
return;
|
||||
case SET_AUDIO_DELAY:
|
||||
player_set_audio_delay(step ? player_audio_delay() + step * 50 : 0);
|
||||
break;
|
||||
@@ -5664,12 +5900,18 @@ void modal_tracks()
|
||||
}
|
||||
for (const std::string &f : files)
|
||||
e.push_back({ 6, 0, trf("Load %s", f.substr(f.rfind('/') + 1).c_str()), false, f });
|
||||
e.push_back({ 7, SET_SUB_PICK, "Add a subtitle file", false, "" });
|
||||
e.push_back({ 7, SET_OSUB, "Download subtitles", false, "" });
|
||||
}
|
||||
if (all || panel_section == SEC_SPEED) {
|
||||
e.push_back({ 0, 0, "Playback", false, "" });
|
||||
e.push_back({ 7, SET_REPEAT, "Loop this video", false, "" });
|
||||
e.push_back({ 7, SET_SHUFFLE, "Shuffle", false, "" });
|
||||
if (playing_disc()) {
|
||||
e.push_back({ 7, SET_DISC_MENU, "Disc menu", false, "" });
|
||||
if (player_disc_has_popup())
|
||||
e.push_back({ 7, SET_POPUP, "Pop-up menu", false, "" });
|
||||
}
|
||||
e.push_back({ 7, SET_SLEEP, "Sleep timer", false, "" });
|
||||
e.push_back({ 7, SET_LOOP, "A-B repeat", false, "" });
|
||||
e.push_back({ 7, SET_BOOKMARK, "Add a bookmark here", false, "" });
|
||||
@@ -5852,7 +6094,7 @@ void modal_tracks()
|
||||
const char *act = id == SET_LOOP ? (loop_a < 0 ? "Mark A" : loop_b < 0 ? "Mark B" : "Clear")
|
||||
: id == SET_SUB_BOX || id == SET_NIGHT || id == SET_SOUND_OUT || id == SET_SHUFFLE ? "Switch"
|
||||
: id == SET_REPEAT ? "Switch" : id == SET_BOOKMARK ? "Add"
|
||||
: id == SET_BM_CLEAR ? "Clear" : "Reset";
|
||||
: id == SET_BM_CLEAR ? "Clear" : id == SET_DISC_MENU || id == SET_POPUP || id == SET_SUB_PICK ? "Open" : "Reset";
|
||||
hints(1920 - 48, 1040, { { PAD_CROSS, act }, { PAD_CIRCLE, "Close" } }, a);
|
||||
}
|
||||
else
|
||||
@@ -6614,16 +6856,23 @@ void player_screen()
|
||||
}
|
||||
/* A disc: the d-pad and ✕ drive its menu while the controls are hidden
|
||||
* (the touchpad brings them up). */
|
||||
int cur_item = current_item();
|
||||
bool disc = (cur_item >= 0 && L()[cur_item].disc) || playing_path.compare(0, 6, "dvd://") == 0 ||
|
||||
playing_path.compare(0, 9, "bluray://") == 0;
|
||||
bool disc = playing_disc();
|
||||
const uint32_t menu_keys = PAD_UP | PAD_DOWN | PAD_LEFT | PAD_RIGHT | PAD_CROSS;
|
||||
bool menu_press = disc && modal == NONE && !osd_was_up && !paused && (pressed & menu_keys);
|
||||
/* A disc menu with buttons on screen (in the film ✕ pauses as usual): the
|
||||
* D-pad and ✕ press its buttons straight away, and the controls stay out
|
||||
* of the way unless the touchpad asked for them. */
|
||||
static bool osd_touch; /* the controls were brought up with the touchpad */
|
||||
if (now >= osd_until)
|
||||
osd_touch = false;
|
||||
bool in_menu = disc && player_in_menu() && modal == NONE && !paused;
|
||||
if (in_menu && !osd_touch)
|
||||
osd_until = std::min(osd_until, now);
|
||||
bool menu_press = in_menu && !(osd_was_up && osd_touch) && (pressed & menu_keys);
|
||||
if (menu_press) {
|
||||
player_navigate(hit(PAD_CROSS) ? 0 : hit(PAD_UP) ? 1 : hit(PAD_DOWN) ? 2 : hit(PAD_LEFT) ? 3 : 4);
|
||||
pressed &= ~menu_keys;
|
||||
}
|
||||
if (pressed && modal == NONE)
|
||||
if (pressed && modal == NONE && (!in_menu || osd_touch))
|
||||
osd_until = now + 4.0;
|
||||
bool osd = now < osd_until || paused || scrubbing;
|
||||
|
||||
@@ -6652,6 +6901,7 @@ void player_screen()
|
||||
return;
|
||||
} else if (hit(PAD_TOUCHPAD)) {
|
||||
osd_until = osd ? now : now + 4.0;
|
||||
osd_touch = !osd;
|
||||
} else if (hit(PAD_L1) || hit(PAD_R1)) {
|
||||
std::vector<Chapter> ch = player_chapters();
|
||||
if (ch.empty()) {
|
||||
@@ -7592,7 +7842,9 @@ void ui_frame(const PadState &pad, float frame_dt)
|
||||
} else {
|
||||
background();
|
||||
tab_bar();
|
||||
if (modal == NONE) {
|
||||
if (sub_pick)
|
||||
tab = BROWSE; /* picking: Browse only */
|
||||
if (modal == NONE && !sub_pick) {
|
||||
if (hit(PAD_L1))
|
||||
tab = (Tab)((tab + TABS - 1) % TABS);
|
||||
if (hit(PAD_R1))
|
||||
@@ -7601,12 +7853,12 @@ void ui_frame(const PadState &pad, float frame_dt)
|
||||
uint32_t keep = pressed;
|
||||
if (modal != NONE)
|
||||
pressed = 0;
|
||||
if (modal == NONE && hit(PAD_R3)) {
|
||||
if (modal == NONE && !sub_pick && hit(PAD_R3)) {
|
||||
open_search();
|
||||
modal_anim = 1;
|
||||
pressed = 0;
|
||||
}
|
||||
if (modal == NONE)
|
||||
if (modal == NONE && !sub_pick)
|
||||
library_actions();
|
||||
switch (tab) {
|
||||
case HOME: tab_home(); break;
|
||||
@@ -7640,8 +7892,15 @@ void ui_frame(const PadState &pad, float frame_dt)
|
||||
h.push_back({ PAD_CIRCLE, "Back" });
|
||||
if (tab == PLAYLISTS)
|
||||
h = playlist_hints();
|
||||
h.push_back({ PAD_R3, "Search" });
|
||||
h.push_back({ PAD_OPTIONS, "Options" });
|
||||
if (sub_pick) {
|
||||
bool file = browse_focus < (int)browse_entries.size() && !browse_entries[browse_focus].dir &&
|
||||
(browse_entries[browse_focus].net == NET_FILE || browse_entries[browse_focus].net == NET_NONE);
|
||||
h = { { PAD_CROSS, file ? "Add" : "Open" },
|
||||
{ PAD_CIRCLE, browse_dir.empty() && net_path.empty() ? "Cancel" : "Back" } };
|
||||
} else {
|
||||
h.push_back({ PAD_R3, "Search" });
|
||||
h.push_back({ PAD_OPTIONS, "Options" });
|
||||
}
|
||||
if (classic_look) {
|
||||
if (tab == PLAYLISTS) {
|
||||
if (pl_open.empty())
|
||||
|
||||
Reference in new issue
Block a user