mirror of
https://github.com/ZiZc3/VLC.git
synced 2026-10-18 14:01:38 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cf91edcf1e | ||
|
|
36d8450e37 | ||
|
|
f109ef6410 | ||
|
|
71306c9256 |
No files matched your search
@@ -107,7 +107,7 @@ Optional full access. With etaHEN, OnionHEN or the Helper, VLC can lift its sand
|
||||
| Hardware decoding (H.264 / HEVC) | ✅ |
|
||||
| HDR → SDR tone mapping | ✅ |
|
||||
| HDR10 output to the TV | ✅ |
|
||||
| Hardware decoding (VP9 up to 4K) | Not yet |
|
||||
| Hardware decoding (VP9 up to 4K) | ✅ |
|
||||
| Upscaling up to 4K (FSR 1, Lanczos, Anime4K etc) | Not yet |
|
||||
|
||||
### 🔊 Audio
|
||||
@@ -192,7 +192,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 |
|
||||
| Blu-ray Java (BD-J) menus | ❌ Java Blu-ray menus not supported |
|
||||
| ZIP, RAR and 7z archives open like folders | ✅ |
|
||||
|
||||
## Building
|
||||
|
||||
@@ -551,3 +551,21 @@ Sign in to the share = تسجيل الدخول إلى المشاركة
|
||||
A Windows PC takes its own account; a NAS, the user you made on it. = كمبيوتر ويندوز يقبل حسابه نفسه، وجهاز NAS يقبل المستخدم الذي أنشأته عليه.
|
||||
Track %d = المسار %d
|
||||
Track = مسار
|
||||
Disc menu = قائمة القرص
|
||||
Pop-up menu = القائمة المنبثقة
|
||||
Blu-ray region = منطقة Blu-ray
|
||||
Region %s = المنطقة %s
|
||||
Blu-ray region: from the next disc you open = منطقة Blu-ray: من القرص التالي الذي تفتحه
|
||||
A: the Americas and East Asia. B: Europe, Africa and Oceania. C: China, Russia and India = A: الأمريكتان وشرق آسيا. B: أوروبا وأفريقيا وأوقيانوسيا. C: الصين وروسيا والهند
|
||||
Add a subtitle file = إضافة ملف ترجمة
|
||||
Pick a subtitle file = اختر ملف ترجمة
|
||||
Subtitles: %s = الترجمة: %s
|
||||
Couldn't load that subtitle file = تعذّر تحميل ملف الترجمة هذا
|
||||
That isn't a subtitle file (.srt, .ass, .vtt...) = هذا ليس ملف ترجمة (.srt و.ass و.vtt...)
|
||||
Java Blu-ray discs (BD-J) aren't supported on the PS5 = أقراص Blu-ray التي تعمل بـ Java (BD-J) غير مدعومة على PS5
|
||||
Hardware decoding = فك الترميز بالعتاد
|
||||
The console's video chip. Off: the processor decodes, slower for 4K = شريحة الفيديو في الجهاز. عند الإيقاف: يفك المعالج الترميز، وهو أبطأ مع 4K
|
||||
Looking for channels and stations… = جارٍ البحث عن القنوات والمحطات…
|
||||
Hardware decoding: from the next video = فك الترميز بالعتاد: من الفيديو التالي
|
||||
Match frame rate = مطابقة معدل الإطارات
|
||||
Films play at their own 24, 25 or 50 Hz: smoother pans. Some TVs go black a moment = تُعرض الأفلام بترددها الأصلي 24 أو 25 أو 50 هرتز: حركة أنعم. بعض أجهزة التلفاز تسودّ لحظة
|
||||
@@ -551,3 +551,21 @@ Sign in to the share = Bei der Freigabe anmelden
|
||||
A Windows PC takes its own account; a NAS, the user you made on it. = Ein Windows-PC nimmt sein eigenes Konto, ein NAS den Benutzer, den du darauf angelegt hast.
|
||||
Track %d = Spur %d
|
||||
Track = Spur
|
||||
Disc menu = Disc-Menü
|
||||
Pop-up menu = Pop-up-Menü
|
||||
Blu-ray region = Blu-ray-Region
|
||||
Region %s = Region %s
|
||||
Blu-ray region: from the next disc you open = Blu-ray-Region: ab der nächsten Disc, die du öffnest
|
||||
A: the Americas and East Asia. B: Europe, Africa and Oceania. C: China, Russia and India = A: Amerika und Ostasien. B: Europa, Afrika und Ozeanien. C: China, Russland und Indien
|
||||
Add a subtitle file = Untertiteldatei hinzufügen
|
||||
Pick a subtitle file = Untertiteldatei wählen
|
||||
Subtitles: %s = Untertitel: %s
|
||||
Couldn't load that subtitle file = Diese Untertiteldatei ließ sich nicht laden
|
||||
That isn't a subtitle file (.srt, .ass, .vtt...) = Das ist keine Untertiteldatei (.srt, .ass, .vtt...)
|
||||
Java Blu-ray discs (BD-J) aren't supported on the PS5 = Java-Blu-rays (BD-J) werden auf der PS5 nicht unterstützt
|
||||
Hardware decoding = Hardware-Dekodierung
|
||||
The console's video chip. Off: the processor decodes, slower for 4K = Der Videochip der Konsole. Aus: Der Prozessor dekodiert, langsamer bei 4K
|
||||
Looking for channels and stations… = Sender und Radiosender werden gesucht…
|
||||
Hardware decoding: from the next video = Hardware-Dekodierung: ab dem nächsten Video
|
||||
Match frame rate = Bildrate anpassen
|
||||
Films play at their own 24, 25 or 50 Hz: smoother pans. Some TVs go black a moment = Filme laufen mit ihren eigenen 24, 25 oder 50 Hz: ruhigere Schwenks. Manche Fernseher werden kurz schwarz
|
||||
@@ -551,3 +551,21 @@ Sign in to the share = Iniciar sesión en la carpeta compartida
|
||||
A Windows PC takes its own account; a NAS, the user you made on it. = Un PC con Windows usa su propia cuenta; un NAS, el usuario que creaste en él.
|
||||
Track %d = Pista %d
|
||||
Track = Pista
|
||||
Disc menu = Menú del disco
|
||||
Pop-up menu = Menú emergente
|
||||
Blu-ray region = Región Blu-ray
|
||||
Region %s = Región %s
|
||||
Blu-ray region: from the next disc you open = Región Blu-ray: desde el próximo disco que abras
|
||||
A: the Americas and East Asia. B: Europe, Africa and Oceania. C: China, Russia and India = A: América y Asia oriental. B: Europa, África y Oceanía. C: China, Rusia e India
|
||||
Add a subtitle file = Añadir un archivo de subtítulos
|
||||
Pick a subtitle file = Elige un archivo de subtítulos
|
||||
Subtitles: %s = Subtítulos: %s
|
||||
Couldn't load that subtitle file = No se pudo cargar ese archivo de subtítulos
|
||||
That isn't a subtitle file (.srt, .ass, .vtt...) = Eso no es un archivo de subtítulos (.srt, .ass, .vtt...)
|
||||
Java Blu-ray discs (BD-J) aren't supported on the PS5 = Los Blu-ray con Java (BD-J) no son compatibles con la PS5
|
||||
Hardware decoding = Decodificación por hardware
|
||||
The console's video chip. Off: the processor decodes, slower for 4K = El chip de vídeo de la consola. Desactivado: decodifica el procesador, más lento en 4K
|
||||
Looking for channels and stations… = Buscando canales y emisoras…
|
||||
Hardware decoding: from the next video = Decodificación por hardware: desde el próximo vídeo
|
||||
Match frame rate = Igualar la frecuencia de imagen
|
||||
Films play at their own 24, 25 or 50 Hz: smoother pans. Some TVs go black a moment = Las películas se ven a sus propios 24, 25 o 50 Hz: paneos más suaves. Algunas TV se ponen en negro un momento
|
||||
@@ -552,3 +552,21 @@ Sign in to the share = Se connecter au partage
|
||||
A Windows PC takes its own account; a NAS, the user you made on it. = Un PC Windows prend son propre compte ; un NAS, l'utilisateur que vous y avez créé.
|
||||
Track %d = Piste %d
|
||||
Track = Piste
|
||||
Disc menu = Menu du disque
|
||||
Pop-up menu = Menu contextuel
|
||||
Blu-ray region = Zone Blu-ray
|
||||
Region %s = Zone %s
|
||||
Blu-ray region: from the next disc you open = Zone Blu-ray : à partir du prochain disque ouvert
|
||||
A: the Americas and East Asia. B: Europe, Africa and Oceania. C: China, Russia and India = A : Amériques et Asie de l'Est. B : Europe, Afrique et Océanie. C : Chine, Russie et Inde
|
||||
Add a subtitle file = Ajouter un fichier de sous-titres
|
||||
Pick a subtitle file = Choisissez un fichier de sous-titres
|
||||
Subtitles: %s = Sous-titres : %s
|
||||
Couldn't load that subtitle file = Impossible de charger ce fichier de sous-titres
|
||||
That isn't a subtitle file (.srt, .ass, .vtt...) = Ce n'est pas un fichier de sous-titres (.srt, .ass, .vtt...)
|
||||
Java Blu-ray discs (BD-J) aren't supported on the PS5 = Les Blu-ray Java (BD-J) ne sont pas pris en charge sur PS5
|
||||
Hardware decoding = Décodage matériel
|
||||
The console's video chip. Off: the processor decodes, slower for 4K = La puce vidéo de la console. Désactivé : le processeur décode, plus lent en 4K
|
||||
Looking for channels and stations… = Recherche de chaînes et de stations…
|
||||
Hardware decoding: from the next video = Décodage matériel : à partir de la prochaine vidéo
|
||||
Match frame rate = Adapter la fréquence d'images
|
||||
Films play at their own 24, 25 or 50 Hz: smoother pans. Some TVs go black a moment = Les films passent à leurs propres 24, 25 ou 50 Hz : panoramiques plus fluides. Certaines TV deviennent noires un instant
|
||||
@@ -551,3 +551,21 @@ Sign in to the share = Masuk ke folder berbagi
|
||||
A Windows PC takes its own account; a NAS, the user you made on it. = PC Windows memakai akunnya sendiri; NAS memakai pengguna yang Anda buat di sana.
|
||||
Track %d = Trek %d
|
||||
Track = Trek
|
||||
Disc menu = Menu disk
|
||||
Pop-up menu = Menu pop-up
|
||||
Blu-ray region = Region Blu-ray
|
||||
Region %s = Region %s
|
||||
Blu-ray region: from the next disc you open = Region Blu-ray: mulai dari disk berikutnya yang kamu buka
|
||||
A: the Americas and East Asia. B: Europe, Africa and Oceania. C: China, Russia and India = A: Amerika dan Asia Timur. B: Eropa, Afrika, dan Oseania. C: Tiongkok, Rusia, dan India
|
||||
Add a subtitle file = Tambahkan file subtitle
|
||||
Pick a subtitle file = Pilih file subtitle
|
||||
Subtitles: %s = Subtitle: %s
|
||||
Couldn't load that subtitle file = File subtitle itu tidak bisa dimuat
|
||||
That isn't a subtitle file (.srt, .ass, .vtt...) = Itu bukan file subtitle (.srt, .ass, .vtt...)
|
||||
Java Blu-ray discs (BD-J) aren't supported on the PS5 = Blu-ray Java (BD-J) tidak didukung di PS5
|
||||
Hardware decoding = Dekode perangkat keras
|
||||
The console's video chip. Off: the processor decodes, slower for 4K = Cip video konsol. Mati: prosesor yang mendekode, lebih lambat untuk 4K
|
||||
Looking for channels and stations… = Mencari saluran dan stasiun…
|
||||
Hardware decoding: from the next video = Dekode perangkat keras: mulai video berikutnya
|
||||
Match frame rate = Samakan laju bingkai
|
||||
Films play at their own 24, 25 or 50 Hz: smoother pans. Some TVs go black a moment = Film diputar pada 24, 25 atau 50 Hz aslinya: gerakan lebih halus. Beberapa TV gelap sesaat
|
||||
@@ -551,3 +551,21 @@ Sign in to the share = Accedi alla condivisione
|
||||
A Windows PC takes its own account; a NAS, the user you made on it. = Un PC Windows usa il proprio account; un NAS, l'utente che hai creato su di esso.
|
||||
Track %d = Traccia %d
|
||||
Track = Traccia
|
||||
Disc menu = Menu del disco
|
||||
Pop-up menu = Menu pop-up
|
||||
Blu-ray region = Area Blu-ray
|
||||
Region %s = Area %s
|
||||
Blu-ray region: from the next disc you open = Area Blu-ray: dal prossimo disco che apri
|
||||
A: the Americas and East Asia. B: Europe, Africa and Oceania. C: China, Russia and India = A: Americhe e Asia orientale. B: Europa, Africa e Oceania. C: Cina, Russia e India
|
||||
Add a subtitle file = Aggiungi un file di sottotitoli
|
||||
Pick a subtitle file = Scegli un file di sottotitoli
|
||||
Subtitles: %s = Sottotitoli: %s
|
||||
Couldn't load that subtitle file = Impossibile caricare quel file di sottotitoli
|
||||
That isn't a subtitle file (.srt, .ass, .vtt...) = Non è un file di sottotitoli (.srt, .ass, .vtt...)
|
||||
Java Blu-ray discs (BD-J) aren't supported on the PS5 = I Blu-ray Java (BD-J) non sono supportati su PS5
|
||||
Hardware decoding = Decodifica hardware
|
||||
The console's video chip. Off: the processor decodes, slower for 4K = Il chip video della console. Disattivato: decodifica il processore, più lento in 4K
|
||||
Looking for channels and stations… = Ricerca di canali e stazioni…
|
||||
Hardware decoding: from the next video = Decodifica hardware: dal prossimo video
|
||||
Match frame rate = Adatta frequenza fotogrammi
|
||||
Films play at their own 24, 25 or 50 Hz: smoother pans. Some TVs go black a moment = I film vanno ai loro 24, 25 o 50 Hz: panoramiche più fluide. Alcune TV diventano nere per un attimo
|
||||
@@ -551,3 +551,21 @@ Sign in to the share = 共有フォルダーにサインイン
|
||||
A Windows PC takes its own account; a NAS, the user you made on it. = Windows PC ならその PC のアカウント、NAS なら NAS で作ったユーザーを使います。
|
||||
Track %d = トラック %d
|
||||
Track = トラック
|
||||
Disc menu = ディスクメニュー
|
||||
Pop-up menu = ポップアップメニュー
|
||||
Blu-ray region = Blu-ray リージョン
|
||||
Region %s = リージョン %s
|
||||
Blu-ray region: from the next disc you open = Blu-ray リージョン: 次に開くディスクから
|
||||
A: the Americas and East Asia. B: Europe, Africa and Oceania. C: China, Russia and India = A: 南北アメリカと東アジア。B: ヨーロッパ、アフリカ、オセアニア。C: 中国、ロシア、インド
|
||||
Add a subtitle file = 字幕ファイルを追加
|
||||
Pick a subtitle file = 字幕ファイルを選択
|
||||
Subtitles: %s = 字幕: %s
|
||||
Couldn't load that subtitle file = その字幕ファイルを読み込めませんでした
|
||||
That isn't a subtitle file (.srt, .ass, .vtt...) = 字幕ファイルではありません (.srt、.ass、.vtt など)
|
||||
Java Blu-ray discs (BD-J) aren't supported on the PS5 = Java の Blu-ray (BD-J) は PS5 では再生できません
|
||||
Hardware decoding = ハードウェアデコード
|
||||
The console's video chip. Off: the processor decodes, slower for 4K = 本体のビデオチップ。オフ:プロセッサーでデコード(4Kでは遅くなります)
|
||||
Looking for channels and stations… = チャンネルと局を検索中…
|
||||
Hardware decoding: from the next video = ハードウェアデコード:次のビデオから
|
||||
Match frame rate = フレームレートを合わせる
|
||||
Films play at their own 24, 25 or 50 Hz: smoother pans. Some TVs go black a moment = 映画を本来の24・25・50Hzで再生し、パンが滑らかになります。一部のテレビは一瞬暗くなります
|
||||
@@ -551,3 +551,21 @@ Sign in to the share = 공유 폴더에 로그인
|
||||
A Windows PC takes its own account; a NAS, the user you made on it. = Windows PC는 그 PC의 계정을, NAS는 NAS에서 만든 사용자를 사용합니다.
|
||||
Track %d = 트랙 %d
|
||||
Track = 트랙
|
||||
Disc menu = 디스크 메뉴
|
||||
Pop-up menu = 팝업 메뉴
|
||||
Blu-ray region = Blu-ray 지역
|
||||
Region %s = 지역 %s
|
||||
Blu-ray region: from the next disc you open = Blu-ray 지역: 다음에 여는 디스크부터
|
||||
A: the Americas and East Asia. B: Europe, Africa and Oceania. C: China, Russia and India = A: 아메리카와 동아시아. B: 유럽, 아프리카, 오세아니아. C: 중국, 러시아, 인도
|
||||
Add a subtitle file = 자막 파일 추가
|
||||
Pick a subtitle file = 자막 파일 선택
|
||||
Subtitles: %s = 자막: %s
|
||||
Couldn't load that subtitle file = 그 자막 파일을 불러올 수 없습니다
|
||||
That isn't a subtitle file (.srt, .ass, .vtt...) = 자막 파일이 아닙니다 (.srt, .ass, .vtt...)
|
||||
Java Blu-ray discs (BD-J) aren't supported on the PS5 = Java 블루레이(BD-J)는 PS5에서 지원되지 않습니다
|
||||
Hardware decoding = 하드웨어 디코딩
|
||||
The console's video chip. Off: the processor decodes, slower for 4K = 본체의 비디오 칩. 끄면 프로세서가 디코딩하며 4K에서는 느립니다
|
||||
Looking for channels and stations… = 채널과 방송국을 찾는 중…
|
||||
Hardware decoding: from the next video = 하드웨어 디코딩: 다음 동영상부터
|
||||
Match frame rate = 프레임 속도 맞추기
|
||||
Films play at their own 24, 25 or 50 Hz: smoother pans. Some TVs go black a moment = 영화를 원래의 24, 25, 50Hz로 재생해 화면 이동이 부드럽습니다. 일부 TV는 잠시 검게 됩니다
|
||||
@@ -551,3 +551,21 @@ Sign in to the share = Aanmelden bij de share
|
||||
A Windows PC takes its own account; a NAS, the user you made on it. = Een Windows-pc gebruikt zijn eigen account; een NAS de gebruiker die je erop hebt gemaakt.
|
||||
Track %d = Track %d
|
||||
Track = Track
|
||||
Disc menu = Schijfmenu
|
||||
Pop-up menu = Pop-upmenu
|
||||
Blu-ray region = Blu-ray-regio
|
||||
Region %s = Regio %s
|
||||
Blu-ray region: from the next disc you open = Blu-ray-regio: vanaf de volgende schijf die je opent
|
||||
A: the Americas and East Asia. B: Europe, Africa and Oceania. C: China, Russia and India = A: Amerika en Oost-Azië. B: Europa, Afrika en Oceanië. C: China, Rusland en India
|
||||
Add a subtitle file = Ondertitelbestand toevoegen
|
||||
Pick a subtitle file = Kies een ondertitelbestand
|
||||
Subtitles: %s = Ondertitels: %s
|
||||
Couldn't load that subtitle file = Dat ondertitelbestand kon niet worden geladen
|
||||
That isn't a subtitle file (.srt, .ass, .vtt...) = Dat is geen ondertitelbestand (.srt, .ass, .vtt...)
|
||||
Java Blu-ray discs (BD-J) aren't supported on the PS5 = Java-Blu-rays (BD-J) worden niet ondersteund op de PS5
|
||||
Hardware decoding = Hardwaredecodering
|
||||
The console's video chip. Off: the processor decodes, slower for 4K = De videochip van de console. Uit: de processor decodeert, trager bij 4K
|
||||
Looking for channels and stations… = Zenders en stations zoeken…
|
||||
Hardware decoding: from the next video = Hardwaredecodering: vanaf de volgende video
|
||||
Match frame rate = Beeldfrequentie aanpassen
|
||||
Films play at their own 24, 25 or 50 Hz: smoother pans. Some TVs go black a moment = Films spelen op hun eigen 24, 25 of 50 Hz: vloeiendere pans. Sommige tv's worden even zwart
|
||||
@@ -551,3 +551,21 @@ Sign in to the share = Zaloguj się do udziału
|
||||
A Windows PC takes its own account; a NAS, the user you made on it. = Komputer z Windows przyjmuje własne konto, a NAS użytkownika, którego na nim utworzono.
|
||||
Track %d = Ścieżka %d
|
||||
Track = Ścieżka
|
||||
Disc menu = Menu płyty
|
||||
Pop-up menu = Menu podręczne
|
||||
Blu-ray region = Region Blu-ray
|
||||
Region %s = Region %s
|
||||
Blu-ray region: from the next disc you open = Region Blu-ray: od następnej otwartej płyty
|
||||
A: the Americas and East Asia. B: Europe, Africa and Oceania. C: China, Russia and India = A: Ameryki i Azja Wschodnia. B: Europa, Afryka i Oceania. C: Chiny, Rosja i Indie
|
||||
Add a subtitle file = Dodaj plik napisów
|
||||
Pick a subtitle file = Wybierz plik napisów
|
||||
Subtitles: %s = Napisy: %s
|
||||
Couldn't load that subtitle file = Nie udało się wczytać tego pliku napisów
|
||||
That isn't a subtitle file (.srt, .ass, .vtt...) = To nie jest plik napisów (.srt, .ass, .vtt...)
|
||||
Java Blu-ray discs (BD-J) aren't supported on the PS5 = Płyty Blu-ray z Javą (BD-J) nie są obsługiwane na PS5
|
||||
Hardware decoding = Dekodowanie sprzętowe
|
||||
The console's video chip. Off: the processor decodes, slower for 4K = Układ wideo konsoli. Wył.: dekoduje procesor, wolniej przy 4K
|
||||
Looking for channels and stations… = Szukanie kanałów i stacji…
|
||||
Hardware decoding: from the next video = Dekodowanie sprzętowe: od następnego filmu
|
||||
Match frame rate = Dopasuj liczbę klatek
|
||||
Films play at their own 24, 25 or 50 Hz: smoother pans. Some TVs go black a moment = Filmy grają we własnych 24, 25 lub 50 Hz: płynniejsze panoramy. Niektóre telewizory na chwilę gasną
|
||||
@@ -551,3 +551,21 @@ Sign in to the share = Entrar no compartilhamento
|
||||
A Windows PC takes its own account; a NAS, the user you made on it. = Um PC com Windows usa a própria conta; um NAS, o usuário que você criou nele.
|
||||
Track %d = Faixa %d
|
||||
Track = Faixa
|
||||
Disc menu = Menu do disco
|
||||
Pop-up menu = Menu pop-up
|
||||
Blu-ray region = Região do Blu-ray
|
||||
Region %s = Região %s
|
||||
Blu-ray region: from the next disc you open = Região do Blu-ray: a partir do próximo disco que você abrir
|
||||
A: the Americas and East Asia. B: Europe, Africa and Oceania. C: China, Russia and India = A: Américas e Leste Asiático. B: Europa, África e Oceania. C: China, Rússia e Índia
|
||||
Add a subtitle file = Adicionar um arquivo de legenda
|
||||
Pick a subtitle file = Escolha um arquivo de legenda
|
||||
Subtitles: %s = Legendas: %s
|
||||
Couldn't load that subtitle file = Não foi possível carregar esse arquivo de legenda
|
||||
That isn't a subtitle file (.srt, .ass, .vtt...) = Isso não é um arquivo de legenda (.srt, .ass, .vtt...)
|
||||
Java Blu-ray discs (BD-J) aren't supported on the PS5 = Blu-rays com Java (BD-J) não são compatíveis com o PS5
|
||||
Hardware decoding = Decodificação por hardware
|
||||
The console's video chip. Off: the processor decodes, slower for 4K = O chip de vídeo do console. Desligado: o processador decodifica, mais lento em 4K
|
||||
Looking for channels and stations… = Procurando canais e estações…
|
||||
Hardware decoding: from the next video = Decodificação por hardware: a partir do próximo vídeo
|
||||
Match frame rate = Igualar taxa de quadros
|
||||
Films play at their own 24, 25 or 50 Hz: smoother pans. Some TVs go black a moment = Filmes rodam nos seus próprios 24, 25 ou 50 Hz: movimentos mais suaves. Algumas TVs ficam pretas por um instante
|
||||
@@ -551,3 +551,21 @@ Sign in to the share = Вход в общую папку
|
||||
A Windows PC takes its own account; a NAS, the user you made on it. = Компьютер с Windows принимает свою учётную запись, NAS — пользователя, которого вы на нём создали.
|
||||
Track %d = Дорожка %d
|
||||
Track = Дорожка
|
||||
Disc menu = Меню диска
|
||||
Pop-up menu = Всплывающее меню
|
||||
Blu-ray region = Регион Blu-ray
|
||||
Region %s = Регион %s
|
||||
Blu-ray region: from the next disc you open = Регион Blu-ray: со следующего открытого диска
|
||||
A: the Americas and East Asia. B: Europe, Africa and Oceania. C: China, Russia and India = A: Америка и Восточная Азия. B: Европа, Африка и Океания. C: Китай, Россия и Индия
|
||||
Add a subtitle file = Добавить файл субтитров
|
||||
Pick a subtitle file = Выберите файл субтитров
|
||||
Subtitles: %s = Субтитры: %s
|
||||
Couldn't load that subtitle file = Не удалось загрузить этот файл субтитров
|
||||
That isn't a subtitle file (.srt, .ass, .vtt...) = Это не файл субтитров (.srt, .ass, .vtt...)
|
||||
Java Blu-ray discs (BD-J) aren't supported on the PS5 = Blu-ray с Java (BD-J) не поддерживаются на PS5
|
||||
Hardware decoding = Аппаратное декодирование
|
||||
The console's video chip. Off: the processor decodes, slower for 4K = Видеочип консоли. Выкл.: декодирует процессор, медленнее для 4K
|
||||
Looking for channels and stations… = Поиск каналов и станций…
|
||||
Hardware decoding: from the next video = Аппаратное декодирование: со следующего видео
|
||||
Match frame rate = Подстройка частоты кадров
|
||||
Films play at their own 24, 25 or 50 Hz: smoother pans. Some TVs go black a moment = Фильмы идут со своей частотой 24, 25 или 50 Гц: плавнее панорамы. Некоторые ТВ на миг гаснут
|
||||
@@ -551,3 +551,21 @@ Sign in to the share = Paylaşıma giriş yap
|
||||
A Windows PC takes its own account; a NAS, the user you made on it. = Windows PC kendi hesabını, NAS ise üzerinde oluşturduğunuz kullanıcıyı kabul eder.
|
||||
Track %d = Parça %d
|
||||
Track = Parça
|
||||
Disc menu = Disk menüsü
|
||||
Pop-up menu = Açılır menü
|
||||
Blu-ray region = Blu-ray bölgesi
|
||||
Region %s = Bölge %s
|
||||
Blu-ray region: from the next disc you open = Blu-ray bölgesi: açacağın bir sonraki diskten itibaren
|
||||
A: the Americas and East Asia. B: Europe, Africa and Oceania. C: China, Russia and India = A: Amerika ve Doğu Asya. B: Avrupa, Afrika ve Okyanusya. C: Çin, Rusya ve Hindistan
|
||||
Add a subtitle file = Altyazı dosyası ekle
|
||||
Pick a subtitle file = Bir altyazı dosyası seç
|
||||
Subtitles: %s = Altyazı: %s
|
||||
Couldn't load that subtitle file = Bu altyazı dosyası yüklenemedi
|
||||
That isn't a subtitle file (.srt, .ass, .vtt...) = Bu bir altyazı dosyası değil (.srt, .ass, .vtt...)
|
||||
Java Blu-ray discs (BD-J) aren't supported on the PS5 = Java Blu-ray diskleri (BD-J) PS5'te desteklenmiyor
|
||||
Hardware decoding = Donanımla kod çözme
|
||||
The console's video chip. Off: the processor decodes, slower for 4K = Konsolun video yongası. Kapalı: işlemci çözer, 4K'da daha yavaş
|
||||
Looking for channels and stations… = Kanallar ve istasyonlar aranıyor…
|
||||
Hardware decoding: from the next video = Donanımla kod çözme: sonraki videodan itibaren
|
||||
Match frame rate = Kare hızını eşle
|
||||
Films play at their own 24, 25 or 50 Hz: smoother pans. Some TVs go black a moment = Filmler kendi 24, 25 veya 50 Hz'lerinde oynar: daha akıcı kaydırmalar. Bazı TV'ler bir an kararır
|
||||
@@ -551,3 +551,21 @@ Sign in to the share = 登录共享文件夹
|
||||
A Windows PC takes its own account; a NAS, the user you made on it. = Windows 电脑用它自己的账户;NAS 用你在上面创建的用户。
|
||||
Track %d = 音轨 %d
|
||||
Track = 音轨
|
||||
Disc menu = 光盘菜单
|
||||
Pop-up menu = 弹出菜单
|
||||
Blu-ray region = 蓝光区域
|
||||
Region %s = 区域 %s
|
||||
Blu-ray region: from the next disc you open = 蓝光区域:从下一张打开的光盘起生效
|
||||
A: the Americas and East Asia. B: Europe, Africa and Oceania. C: China, Russia and India = A:美洲和东亚。B:欧洲、非洲和大洋洲。C:中国、俄罗斯和印度
|
||||
Add a subtitle file = 添加字幕文件
|
||||
Pick a subtitle file = 选择字幕文件
|
||||
Subtitles: %s = 字幕:%s
|
||||
Couldn't load that subtitle file = 无法加载该字幕文件
|
||||
That isn't a subtitle file (.srt, .ass, .vtt...) = 这不是字幕文件(.srt、.ass、.vtt…)
|
||||
Java Blu-ray discs (BD-J) aren't supported on the PS5 = PS5 不支持 Java 蓝光光盘 (BD-J)
|
||||
Hardware decoding = 硬件解码
|
||||
The console's video chip. Off: the processor decodes, slower for 4K = 主机的视频芯片。关闭:由处理器解码,4K 较慢
|
||||
Looking for channels and stations… = 正在查找频道和电台…
|
||||
Hardware decoding: from the next video = 硬件解码:从下一个视频开始
|
||||
Match frame rate = 匹配帧率
|
||||
Films play at their own 24, 25 or 50 Hz: smoother pans. Some TVs go black a moment = 电影以其本身的 24、25 或 50 Hz 播放:平移更流畅。部分电视会黑屏片刻
|
||||
@@ -551,3 +551,21 @@ Sign in to the share = 登入共用資料夾
|
||||
A Windows PC takes its own account; a NAS, the user you made on it. = Windows 電腦用它自己的帳戶;NAS 用你在上面建立的使用者。
|
||||
Track %d = 音軌 %d
|
||||
Track = 音軌
|
||||
Disc menu = 光碟選單
|
||||
Pop-up menu = 彈出式選單
|
||||
Blu-ray region = 藍光區碼
|
||||
Region %s = 區碼 %s
|
||||
Blu-ray region: from the next disc you open = 藍光區碼:從下一張開啟的光碟起生效
|
||||
A: the Americas and East Asia. B: Europe, Africa and Oceania. C: China, Russia and India = A:美洲與東亞。B:歐洲、非洲與大洋洲。C:中國、俄羅斯與印度
|
||||
Add a subtitle file = 加入字幕檔
|
||||
Pick a subtitle file = 選擇字幕檔
|
||||
Subtitles: %s = 字幕:%s
|
||||
Couldn't load that subtitle file = 無法載入該字幕檔
|
||||
That isn't a subtitle file (.srt, .ass, .vtt...) = 這不是字幕檔(.srt、.ass、.vtt…)
|
||||
Java Blu-ray discs (BD-J) aren't supported on the PS5 = PS5 不支援 Java 藍光光碟 (BD-J)
|
||||
Hardware decoding = 硬體解碼
|
||||
The console's video chip. Off: the processor decodes, slower for 4K = 主機的視訊晶片。關閉:由處理器解碼,4K 較慢
|
||||
Looking for channels and stations… = 正在尋找頻道和電台…
|
||||
Hardware decoding: from the next video = 硬體解碼:從下一個影片開始
|
||||
Match frame rate = 配合影格率
|
||||
Films play at their own 24, 25 or 50 Hz: smoother pans. Some TVs go black a moment = 電影以其原本的 24、25 或 50 Hz 播放:平移更流暢。部分電視會黑屏片刻
|
||||
@@ -0,0 +1,38 @@
|
||||
--- a/modules/video_output/vmem.c
|
||||
+++ b/modules/video_output/vmem.c
|
||||
@@ -145,6 +145,8 @@
|
||||
var_InheritAddress(vd, "vmem-colour");
|
||||
if (colour != NULL)
|
||||
colour(sys->opaque, &fmt);
|
||||
+ /* VLC-PS5: the decoder's own format, for its visible part below */
|
||||
+ const video_format_t source = fmt;
|
||||
|
||||
if (setup != NULL) {
|
||||
char chroma[5];
|
||||
@@ -178,9 +180,23 @@
|
||||
}
|
||||
sys->cleanup = NULL;
|
||||
}
|
||||
- fmt.i_x_offset = fmt.i_y_offset = 0;
|
||||
- fmt.i_visible_width = fmt.i_width;
|
||||
- fmt.i_visible_height = fmt.i_height;
|
||||
+ /* VLC-PS5: a buffer at the decoder's own size keeps the decoder's
|
||||
+ * visible part (1280x720 in 1280x736; 3840x2160 in 3840x2176): called
|
||||
+ * all visible, it made VLC scale every picture on the CPU to fill it
|
||||
+ * (swscale, a whole 4K picture per frame in software decoding). The
|
||||
+ * app crops it when drawing. */
|
||||
+ if (fmt.i_width == source.i_width && fmt.i_height == source.i_height &&
|
||||
+ source.i_x_offset == 0 && source.i_y_offset == 0 &&
|
||||
+ source.i_visible_width > 0 && source.i_visible_height > 0 &&
|
||||
+ source.i_visible_width <= fmt.i_width && source.i_visible_height <= fmt.i_height) {
|
||||
+ fmt.i_x_offset = fmt.i_y_offset = 0;
|
||||
+ fmt.i_visible_width = source.i_visible_width;
|
||||
+ fmt.i_visible_height = source.i_visible_height;
|
||||
+ } else {
|
||||
+ fmt.i_x_offset = fmt.i_y_offset = 0;
|
||||
+ fmt.i_visible_width = fmt.i_width;
|
||||
+ fmt.i_visible_height = fmt.i_height;
|
||||
+ }
|
||||
|
||||
if (!fmt.i_chroma) {
|
||||
msg_Err(vd, "vmem-chroma should be 4 characters long");
|
||||
@@ -0,0 +1,41 @@
|
||||
--- a/modules/codec/avcodec/video.c
|
||||
+++ b/modules/codec/avcodec/video.c
|
||||
@@ -84,6 +84,9 @@
|
||||
/* Hack to force display of still pictures */
|
||||
bool b_first_frame;
|
||||
|
||||
+ /* VLC-PS5: pictures refused in a row (see DecodeBlock) */
|
||||
+ int ps5_refused;
|
||||
+
|
||||
bool b_draining;
|
||||
|
||||
/* */
|
||||
@@ -1244,6 +1247,19 @@
|
||||
}
|
||||
|
||||
ret = avcodec_send_packet(p_context, pkt);
|
||||
+ if( ret == AVERROR(ENOMEM) && p_sys->ps5_refused < 200 )
|
||||
+ {
|
||||
+ /* VLC-PS5: no picture for a moment (the pictures are cancelled
|
||||
+ * at a live stream's break, while the decoder's threads still
|
||||
+ * ask for one): that packet is lost, and the video goes on
|
||||
+ * from the next keyframe instead of ending (BBC America's
|
||||
+ * picture stopped for good at a segment break, 2026-10-13) */
|
||||
+ if (p_sys->ps5_refused++ == 0)
|
||||
+ msg_Warn(p_dec, "avcodec_send_packet: no picture for now, going on from the next keyframe");
|
||||
+ avcodec_flush_buffers(p_context);
|
||||
+ av_packet_free( &pkt );
|
||||
+ break;
|
||||
+ }
|
||||
if( ret != 0 && ret != AVERROR(EAGAIN) )
|
||||
{
|
||||
if (ret == AVERROR(ENOMEM) || ret == AVERROR(EINVAL))
|
||||
@@ -1283,6 +1299,8 @@
|
||||
break;
|
||||
}
|
||||
bool not_received_frame = ret;
|
||||
+ if (!not_received_frame)
|
||||
+ p_sys->ps5_refused = 0;
|
||||
|
||||
wait_mt( p_sys );
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
--- a/modules/demux/adaptive/PlaylistManager.cpp
|
||||
+++ b/modules/demux/adaptive/PlaylistManager.cpp
|
||||
@@ -685,8 +685,14 @@
|
||||
}
|
||||
|
||||
case DEMUX_GET_PTS_DELAY:
|
||||
- *va_arg (args, int64_t *) = 1000 * INT64_C(1000);
|
||||
+ {
|
||||
+ /* VLC-PS5: the player's network caching, not always 1 s: a
|
||||
+ * channel whose server sends barely in real time stalled and
|
||||
+ * rebuffered each time (BBC America, 2026-10-18) */
|
||||
+ int64_t ms = var_InheritInteger(p_demux, "network-caching");
|
||||
+ *va_arg (args, int64_t *) = (ms > 1000 ? ms : 1000) * INT64_C(1000);
|
||||
break;
|
||||
+ }
|
||||
|
||||
default:
|
||||
return VLC_EGENERIC;
|
||||
@@ -0,0 +1,58 @@
|
||||
--- a/modules/demux/adaptive/http/HTTPConnection.cpp
|
||||
+++ b/modules/demux/adaptive/http/HTTPConnection.cpp
|
||||
@@ -42,6 +42,19 @@
|
||||
|
||||
using namespace adaptive::http;
|
||||
|
||||
+/* VLC-PS5: the player stopped this video (src/player.cc). Without this,
|
||||
+ * after a stop the next segment was still fetched, by VLC's own http once
|
||||
+ * the console's refused it, and leaving a slow channel took 10 s; the next
|
||||
+ * video waited for it (2026-10-18). */
|
||||
+extern "C" int vlc_ps5_stream_cancelled(long) __attribute__((weak));
|
||||
+static bool ps5_cancelled(vlc_object_t *obj)
|
||||
+{
|
||||
+ if (vlc_ps5_stream_cancelled == nullptr)
|
||||
+ return false;
|
||||
+ long serial = (long)var_InheritInteger(obj, "ps5http-serial");
|
||||
+ return serial > 0 && vlc_ps5_stream_cancelled(serial);
|
||||
+}
|
||||
+
|
||||
AbstractConnection::AbstractConnection(vlc_object_t *p_object_)
|
||||
{
|
||||
p_object = p_object_;
|
||||
@@ -295,7 +308,7 @@
|
||||
RequestStatus LibVLCHTTPConnection::request(const std::string &path,
|
||||
const BytesRange &range)
|
||||
{
|
||||
- if(source->http_mgr == nullptr)
|
||||
+ if(source->http_mgr == nullptr || ps5_cancelled(p_object))
|
||||
return RequestStatus::GenericError;
|
||||
|
||||
reset();
|
||||
@@ -414,6 +427,8 @@
|
||||
|
||||
ssize_t LibVLCHTTPConnection::read(void *p_buffer, size_t len)
|
||||
{
|
||||
+ if (ps5_cancelled(p_object))
|
||||
+ return 0;
|
||||
ssize_t read = vlc_stream_Read(stream, p_buffer, len);
|
||||
bytesRead = source->totalRead;
|
||||
return read;
|
||||
@@ -462,6 +477,8 @@
|
||||
RequestStatus StreamUrlConnection::request(const std::string &path,
|
||||
const BytesRange &range)
|
||||
{
|
||||
+ if (ps5_cancelled(p_object))
|
||||
+ return RequestStatus::GenericError;
|
||||
reset();
|
||||
|
||||
/* Set new path for this query */
|
||||
@@ -509,6 +526,8 @@
|
||||
{
|
||||
if( !p_streamurl )
|
||||
return VLC_EGENERIC;
|
||||
+ if (ps5_cancelled(p_object))
|
||||
+ return 0;
|
||||
|
||||
if(len == 0)
|
||||
return VLC_SUCCESS;
|
||||
@@ -0,0 +1,16 @@
|
||||
--- a/src/input/clock.c
|
||||
+++ b/src/input/clock.c
|
||||
@@ -80,7 +80,12 @@
|
||||
*****************************************************************************/
|
||||
|
||||
/* Maximum gap allowed between two CRs. */
|
||||
-#define CR_MAX_GAP (60 * CLOCK_FREQ)
|
||||
+/* VLC-PS5: 10 s, not 60. Between two clock references of one stream
|
||||
+ * (sent every 100 ms or so) a jump over 10 s is a new start; at 60 s a free
|
||||
+ * TV channel's ad break that moved its times by 35 s was taken as playing
|
||||
+ * on, every picture and sound counted 35 s late and thrown away, and the
|
||||
+ * picture froze (a Pluto channel, 2026-10-18). */
|
||||
+#define CR_MAX_GAP (10 * CLOCK_FREQ)
|
||||
|
||||
/* Latency introduced on DVDs with CR == 0 on chapter change - this is from
|
||||
* my dice --Meuuh */
|
||||
@@ -0,0 +1,15 @@
|
||||
--- a/modules/demux/adaptive/logic/BufferingLogic.cpp
|
||||
+++ b/modules/demux/adaptive/logic/BufferingLogic.cpp
|
||||
@@ -38,7 +38,11 @@
|
||||
using namespace adaptive::logic;
|
||||
|
||||
const vlc_tick_t AbstractBufferingLogic::BUFFERING_LOWEST_LIMIT = CLOCK_FREQ * 2;
|
||||
-const vlc_tick_t AbstractBufferingLogic::DEFAULT_MIN_BUFFERING = CLOCK_FREQ * 6;
|
||||
+/* VLC-PS5: 3 s before a live channel starts, not 6: on the console's Wi-Fi
|
||||
+ * (about 700 KiB/s to far servers) 6 s of an 8 Mbit/s channel took 4-5 s to
|
||||
+ * come in, and channels were slow to show (2026-10-18). It still fills up
|
||||
+ * to its live delay while it plays. */
|
||||
+const vlc_tick_t AbstractBufferingLogic::DEFAULT_MIN_BUFFERING = CLOCK_FREQ * 3;
|
||||
const vlc_tick_t AbstractBufferingLogic::DEFAULT_MAX_BUFFERING = CLOCK_FREQ * 30;
|
||||
const vlc_tick_t AbstractBufferingLogic::DEFAULT_LIVE_BUFFERING = CLOCK_FREQ * 15;
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
/* The display and its modes: one each per process, as VideoOut is. */
|
||||
struct wsi_videoout_mode {
|
||||
uint32_t refresh_millihertz;
|
||||
@@ -399,6 +437,54 @@
|
||||
@@ -399,6 +437,89 @@
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -98,6 +98,41 @@
|
||||
+ mtx_unlock(&out->lock);
|
||||
+ return result;
|
||||
+}
|
||||
+
|
||||
+/*
|
||||
+ * VLC-PS5: the output's refresh for films, as VideoOut's output modes number
|
||||
+ * it: 2 (23.976 Hz), 3 (24 Hz), 9 (50 Hz), 1 (back to the default). Asked
|
||||
+ * first whether the output takes it; then the vblank period measured, in
|
||||
+ * microseconds, into *period_us (0 if not changed). 0 when VideoOut took it.
|
||||
+ */
|
||||
+int vlcps5_wsi_videoout_set_refresh(int mode, int *period_us);
|
||||
+
|
||||
+int
|
||||
+vlcps5_wsi_videoout_set_refresh(int mode, int *period_us)
|
||||
+{
|
||||
+ struct wsi_videoout_output *out = &videoout_output;
|
||||
+ int result = -1;
|
||||
+ if (period_us)
|
||||
+ *period_us = 0;
|
||||
+ mtx_lock(&out->lock);
|
||||
+ if (videoout_open_locked()) {
|
||||
+ const int supported = mode == (int)PS5_VIDEO_OUT_MODE_RESTORE
|
||||
+ ? 1
|
||||
+ : sceVideoOutIsOutputSupported(out->handle, (uint32_t)mode, NULL, NULL, NULL);
|
||||
+ result = supported > 0 ? sceVideoOutConfigureOutput(out->handle, (uint32_t)mode, NULL, NULL, NULL)
|
||||
+ : (supported < 0 ? supported : -2);
|
||||
+ int measured = 0;
|
||||
+ if (result == 0)
|
||||
+ measured = (int)(videoout_vblank_period_ns(out->handle) / 1000);
|
||||
+ if (period_us)
|
||||
+ *period_us = measured;
|
||||
+ fprintf(stderr, "wsi/videoout: output mode %d: %s (0x%08x), a vblank every %.3f ms\n", mode,
|
||||
+ result == 0 ? "taken" : supported > 0 ? "refused" : "not supported", (unsigned)result,
|
||||
+ measured / 1000.0);
|
||||
+ }
|
||||
+ mtx_unlock(&out->lock);
|
||||
+ return result;
|
||||
+}
|
||||
+
|
||||
static int64_t
|
||||
videoout_shown_argument_locked(void)
|
||||
|
||||
+7
-3
@@ -226,7 +226,7 @@ int vlc_ps5_big_free(void *addr)
|
||||
for (int i = 0; i < BIG_MAX && !found; i++) {
|
||||
if (big_maps[i].addr && big_maps[i].addr == addr) {
|
||||
if (big_maps[i].start < 0) {
|
||||
sceKernelReleaseFlexibleMemory(addr, big_maps[i].len);
|
||||
sceKernelMunmap(addr, big_maps[i].len); /* (released, it never came back) */
|
||||
big_maps[i].addr = NULL;
|
||||
found = 1;
|
||||
break;
|
||||
@@ -339,7 +339,7 @@ void *__wrap_mmap(void *addr, size_t len, int prot, int flags, int fd,
|
||||
return MAP_FAILED;
|
||||
}
|
||||
if ((flags & MAP_FIXED) && result != addr) {
|
||||
sceKernelReleaseFlexibleMemory(result, len);
|
||||
sceKernelMunmap(result, len);
|
||||
errno = EINVAL;
|
||||
return MAP_FAILED;
|
||||
}
|
||||
@@ -352,7 +352,11 @@ int __wrap_munmap(void *addr, size_t len)
|
||||
if (unmap_direct(addr, len)) {
|
||||
return 0;
|
||||
}
|
||||
if (sceKernelReleaseFlexibleMemory(addr, len) == 0) {
|
||||
/* Flexible memory comes back with munmap, not with
|
||||
* sceKernelReleaseFlexibleMemory: released, it stayed taken (the app's
|
||||
* self-test on the console, 2026-10-13: 32 MiB released, 0 back; 32 MiB
|
||||
* unmapped, all back). Every big free VLC and FFmpeg made leaked so. */
|
||||
if (sceKernelMunmap(addr, len) == 0) {
|
||||
return 0;
|
||||
}
|
||||
/* A reserved range, or a file mapping. */
|
||||
|
||||
+498
-7
@@ -17,7 +17,12 @@
|
||||
|
||||
#include <errno.h>
|
||||
#include <inttypes.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <netdb.h>
|
||||
#include <netinet/in.h>
|
||||
#include <pthread.h>
|
||||
#include <sys/socket.h>
|
||||
#include <time.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@@ -27,6 +32,7 @@
|
||||
#include <vlc_common.h>
|
||||
#include <vlc_access.h>
|
||||
#include <vlc_interrupt.h>
|
||||
#include <stdatomic.h>
|
||||
#include <vlc_plugin.h>
|
||||
#include <vlc_url.h>
|
||||
|
||||
@@ -69,13 +75,76 @@ static int http_template = -1;
|
||||
/* The console refused a request outright (run 15: 0x8095f00c on every https
|
||||
* request): VLC's own http module does the rest of the session's. */
|
||||
static bool service_refused;
|
||||
/* failures in a row that no answer came between: the service itself only
|
||||
* after a few (one cancelled search turned it off for the whole session and
|
||||
* the next channel went to VLC's own http, 2026-10-18) */
|
||||
static atomic_int service_failures;
|
||||
static atomic_int https_name_refusals;
|
||||
|
||||
/* Servers the console's SSL turned down: straight to VLC's own http next
|
||||
* time (every segment of a channel paid a refused attempt first). After
|
||||
* several different ones it refuses https as a whole this session (a log of
|
||||
* 2026-10-13: every https server refused, GitHub too). */
|
||||
#define REFUSED_MAX 64
|
||||
static vlc_mutex_t refused_lock = VLC_STATIC_MUTEX;
|
||||
static char *refused_hosts[REFUSED_MAX];
|
||||
static unsigned refused_count;
|
||||
static bool https_refused;
|
||||
|
||||
static char *url_host(const char *url)
|
||||
{
|
||||
const char *p = strstr(url, "://");
|
||||
if (!p)
|
||||
return NULL;
|
||||
p += 3;
|
||||
size_t n = strcspn(p, "/?#");
|
||||
return strndup(p, n);
|
||||
}
|
||||
|
||||
static bool host_refused(const char *url)
|
||||
{
|
||||
if (strncasecmp(url, "https:", 6))
|
||||
return false;
|
||||
char *host = url_host(url);
|
||||
bool refused = false;
|
||||
vlc_mutex_lock(&refused_lock);
|
||||
refused = https_refused;
|
||||
for (unsigned i = 0; host && !refused && i < refused_count; i++)
|
||||
refused = !strcasecmp(refused_hosts[i], host);
|
||||
vlc_mutex_unlock(&refused_lock);
|
||||
free(host);
|
||||
return refused;
|
||||
}
|
||||
|
||||
static unsigned ssl_refused_count;
|
||||
|
||||
static void refuse_host(const char *url, bool ssl)
|
||||
{
|
||||
char *host = url_host(url);
|
||||
if (!host)
|
||||
return;
|
||||
vlc_mutex_lock(&refused_lock);
|
||||
bool known = false;
|
||||
for (unsigned i = 0; i < refused_count; i++)
|
||||
known = known || !strcasecmp(refused_hosts[i], host);
|
||||
if (!known && refused_count < REFUSED_MAX) {
|
||||
refused_hosts[refused_count++] = host;
|
||||
host = NULL;
|
||||
if (ssl && ++ssl_refused_count == 4) {
|
||||
https_refused = true;
|
||||
fprintf(stderr, "ps5http: SSL refused 4 different servers: VLC's own http takes every https link\n");
|
||||
}
|
||||
}
|
||||
vlc_mutex_unlock(&refused_lock);
|
||||
free(host);
|
||||
}
|
||||
|
||||
static void start_service(void)
|
||||
{
|
||||
sceNetInit();
|
||||
int pool = sceNetPoolCreate("vlcps5_http", 1024 * 1024, 0);
|
||||
int pool = sceNetPoolCreate("vlcps5_http", 4 * 1024 * 1024, 0); /* room for parallel pieces */
|
||||
int ssl = sceSslInit(320 * 1024);
|
||||
int http = pool >= 0 && ssl >= 0 ? sceHttpInit(pool, ssl, 4 * 1024 * 1024) : -1;
|
||||
int http = pool >= 0 && ssl >= 0 ? sceHttpInit(pool, ssl, 8 * 1024 * 1024) : -1;
|
||||
if (http < 0) {
|
||||
fprintf(stderr, "ps5http: the console's HTTP service didn't start (net %d, ssl %d, http %d)\n",
|
||||
pool, ssl, http);
|
||||
@@ -106,8 +175,24 @@ typedef struct ps5http_sys {
|
||||
char *content_type;
|
||||
char *redirect; /* Location of a 3xx */
|
||||
int status;
|
||||
long serial; /* the player's open this belongs to (0: none) */
|
||||
bool logged_direct;
|
||||
mtime_t opened_at; /* for the speed in the log */
|
||||
uint64_t read_bytes;
|
||||
mtime_t read_wait; /* time spent inside the console's read */
|
||||
bool cancel_logged;
|
||||
struct parallel *par; /* a segment fetched over several connections, or NULL */
|
||||
} ps5http_sys;
|
||||
|
||||
/* The player (src/player.cc): its open number was stopped */
|
||||
extern int vlc_ps5_stream_cancelled(long serial) __attribute__((weak));
|
||||
extern int vlc_ps5_log_details(void) __attribute__((weak));
|
||||
|
||||
static bool cancelled(ps5http_sys *sys)
|
||||
{
|
||||
return sys->serial && vlc_ps5_stream_cancelled && vlc_ps5_stream_cancelled(sys->serial);
|
||||
}
|
||||
|
||||
static void drop_request(ps5http_sys *sys)
|
||||
{
|
||||
if (sys->req >= 0)
|
||||
@@ -147,6 +232,303 @@ static void abort_on_interrupt(void *data)
|
||||
sceHttpAbortRequest(*req);
|
||||
}
|
||||
|
||||
/* The app's own name lookup (ps5/compat/vlc_resolver.c): the console's
|
||||
* DNS can be one that blocks (a PS5 set up to keep away from updates sends
|
||||
* names to an address that refuses): every named server failed with
|
||||
* 0x8041013d while bare addresses worked (2026-10-18). */
|
||||
extern int vlcps5_getaddrinfo(const char *node, const char *service, const struct addrinfo *hints,
|
||||
struct addrinfo **res);
|
||||
extern void vlcps5_freeaddrinfo(struct addrinfo *ai);
|
||||
static atomic_bool direct_broken; /* a server answered an address-only request wrongly */
|
||||
|
||||
/* An http:// link with a name: the same link with the address our lookup
|
||||
* gives, and the name for the Host header ("name" or "name:port"). NULL: as
|
||||
* it is (https: the secure connection needs the name; an address already). */
|
||||
static char *direct_url(const char *url, char **host_header)
|
||||
{
|
||||
*host_header = NULL;
|
||||
if (atomic_load(&direct_broken) || strncasecmp(url, "http://", 7))
|
||||
return NULL;
|
||||
const char *h = url + 7;
|
||||
size_t hlen = strcspn(h, "/?#");
|
||||
if (!hlen || h[0] == '[' || memchr(h, '@', hlen))
|
||||
return NULL;
|
||||
char *hostport = strndup(h, hlen);
|
||||
if (!hostport)
|
||||
return NULL;
|
||||
char *colon = strchr(hostport, ':');
|
||||
char name[256];
|
||||
snprintf(name, sizeof(name), "%.*s", colon ? (int)(colon - hostport) : (int)hlen, hostport);
|
||||
struct in_addr ip;
|
||||
if (inet_pton(AF_INET, name, &ip) == 1) {
|
||||
free(hostport);
|
||||
return NULL;
|
||||
}
|
||||
struct addrinfo hints = { .ai_family = AF_INET, .ai_socktype = SOCK_STREAM }, *res = NULL;
|
||||
if (vlcps5_getaddrinfo(name, NULL, &hints, &res) != 0 || !res) {
|
||||
free(hostport);
|
||||
return NULL;
|
||||
}
|
||||
char addr[INET_ADDRSTRLEN];
|
||||
inet_ntop(AF_INET, &((struct sockaddr_in *)res->ai_addr)->sin_addr, addr, sizeof(addr));
|
||||
vlcps5_freeaddrinfo(res);
|
||||
char *out = NULL;
|
||||
if (asprintf(&out, "http://%s%s%s", addr, colon ? colon : "", h + hlen) < 0) {
|
||||
free(hostport);
|
||||
return NULL;
|
||||
}
|
||||
*host_header = hostport;
|
||||
return out;
|
||||
}
|
||||
|
||||
/* ---- a whole segment over several connections ------------------------------
|
||||
*
|
||||
* One connection to a far server is often slower than the line: the console
|
||||
* read BBC America at a steady 700 KiB/s, a 4 s segment in 5 s, and the
|
||||
* channel froze while the same server played on a PC (2026-10-18). A file of
|
||||
* a few MiB served with byte ranges (a live channel's segment) is fetched by
|
||||
* PAR_WORKERS connections at once, PAR_CHUNK at a time, into memory; VLC
|
||||
* reads it in order as the pieces come. */
|
||||
#define PAR_CHUNK ((uint64_t)512 << 10)
|
||||
#define PAR_WORKERS 4
|
||||
#define PAR_MIN ((uint64_t)1 << 20) /* smaller: one connection is enough */
|
||||
#define PAR_MAX ((uint64_t)24 << 20) /* bigger (films): one connection, as before */
|
||||
|
||||
typedef struct parallel {
|
||||
char *url, *ua, *ref;
|
||||
long serial;
|
||||
uint8_t *buf;
|
||||
uint64_t size;
|
||||
unsigned chunks, next;
|
||||
uint32_t *filled; /* bytes in so far, per chunk */
|
||||
bool *failed;
|
||||
bool stop;
|
||||
int reqs[PAR_WORKERS]; /* each worker's request, for an abort */
|
||||
pthread_t threads[PAR_WORKERS];
|
||||
int nthreads;
|
||||
uint64_t fetched;
|
||||
pthread_mutex_t lock;
|
||||
pthread_cond_t cond;
|
||||
} parallel;
|
||||
|
||||
static void add_headers(int req, const char *host_header, const char *ua, const char *ref)
|
||||
{
|
||||
if (host_header)
|
||||
sceHttpAddRequestHeader(req, "Host", host_header, HEADER_OVERWRITE);
|
||||
if (ua && *ua)
|
||||
sceHttpAddRequestHeader(req, "User-Agent", ua, HEADER_OVERWRITE);
|
||||
if (ref && *ref)
|
||||
sceHttpAddRequestHeader(req, "Referer", ref, HEADER_OVERWRITE);
|
||||
sceHttpAddRequestHeader(req, "Accept", "*/*", HEADER_OVERWRITE);
|
||||
sceHttpAddRequestHeader(req, "Cache-Control", "no-cache", HEADER_OVERWRITE);
|
||||
sceHttpAddRequestHeader(req, "Pragma", "no-cache", HEADER_OVERWRITE);
|
||||
}
|
||||
|
||||
static bool par_cancelled(parallel *p)
|
||||
{
|
||||
return p->serial && vlc_ps5_stream_cancelled && vlc_ps5_stream_cancelled(p->serial);
|
||||
}
|
||||
|
||||
/* One piece, from where it got to: true when it's all in. */
|
||||
static bool par_fetch(parallel *p, int w, unsigned idx)
|
||||
{
|
||||
uint64_t start = idx * PAR_CHUNK, end = start + PAR_CHUNK;
|
||||
if (end > p->size)
|
||||
end = p->size;
|
||||
pthread_mutex_lock(&p->lock);
|
||||
uint64_t from = start + p->filled[idx];
|
||||
pthread_mutex_unlock(&p->lock);
|
||||
if (from >= end)
|
||||
return true;
|
||||
char *host_header = NULL;
|
||||
char *direct = direct_url(p->url, &host_header);
|
||||
const char *url = direct ? direct : p->url;
|
||||
int conn = sceHttpCreateConnectionWithURL(http_template, url, 1);
|
||||
int req = conn >= 0 ? sceHttpCreateRequestWithURL(conn, METHOD_GET, url, 0) : -1;
|
||||
free(direct);
|
||||
bool ok = false;
|
||||
if (req >= 0) {
|
||||
add_headers(req, host_header, p->ua, p->ref);
|
||||
char range[64];
|
||||
snprintf(range, sizeof(range), "bytes=%" PRIu64 "-%" PRIu64, from, end - 1);
|
||||
sceHttpAddRequestHeader(req, "Range", range, HEADER_OVERWRITE);
|
||||
pthread_mutex_lock(&p->lock);
|
||||
p->reqs[w] = p->stop ? -1 : req;
|
||||
bool stop = p->stop;
|
||||
pthread_mutex_unlock(&p->lock);
|
||||
int status = 0;
|
||||
if (!stop && sceHttpSendRequest(req, NULL, 0) >= 0 && sceHttpGetStatusCode(req, &status) >= 0 &&
|
||||
(status == 206 || (status == 200 && from == 0))) {
|
||||
ok = true;
|
||||
while (from < end) {
|
||||
size_t want = end - from > (64u << 10) ? (64u << 10) : (size_t)(end - from);
|
||||
int n = sceHttpReadData(req, p->buf + from, want);
|
||||
if (n <= 0) {
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
from += (uint64_t)n;
|
||||
pthread_mutex_lock(&p->lock);
|
||||
p->filled[idx] += (uint32_t)n;
|
||||
p->fetched += (uint64_t)n;
|
||||
stop = p->stop;
|
||||
pthread_cond_broadcast(&p->cond);
|
||||
pthread_mutex_unlock(&p->lock);
|
||||
if (stop || par_cancelled(p)) {
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
pthread_mutex_lock(&p->lock);
|
||||
p->reqs[w] = -1;
|
||||
pthread_mutex_unlock(&p->lock);
|
||||
sceHttpDeleteRequest(req);
|
||||
}
|
||||
if (conn >= 0)
|
||||
sceHttpDeleteConnection(conn);
|
||||
free(host_header);
|
||||
return ok;
|
||||
}
|
||||
|
||||
struct par_worker {
|
||||
parallel *p;
|
||||
int w;
|
||||
};
|
||||
|
||||
static void *par_run(void *arg)
|
||||
{
|
||||
struct par_worker *pw = arg;
|
||||
parallel *p = pw->p;
|
||||
int w = pw->w;
|
||||
free(pw);
|
||||
for (;;) {
|
||||
pthread_mutex_lock(&p->lock);
|
||||
if (p->stop || p->next >= p->chunks) {
|
||||
pthread_mutex_unlock(&p->lock);
|
||||
break;
|
||||
}
|
||||
unsigned idx = p->next++;
|
||||
pthread_mutex_unlock(&p->lock);
|
||||
bool ok = false;
|
||||
for (int tries = 0; tries < 3 && !ok && !p->stop && !par_cancelled(p); tries++)
|
||||
ok = par_fetch(p, w, idx);
|
||||
if (!ok) {
|
||||
pthread_mutex_lock(&p->lock);
|
||||
p->failed[idx] = true;
|
||||
pthread_cond_broadcast(&p->cond);
|
||||
pthread_mutex_unlock(&p->lock);
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void par_free(parallel *p)
|
||||
{
|
||||
if (!p)
|
||||
return;
|
||||
pthread_mutex_lock(&p->lock);
|
||||
p->stop = true;
|
||||
for (int w = 0; w < PAR_WORKERS; w++)
|
||||
if (p->reqs[w] >= 0)
|
||||
sceHttpAbortRequest(p->reqs[w]);
|
||||
pthread_cond_broadcast(&p->cond);
|
||||
pthread_mutex_unlock(&p->lock);
|
||||
for (int i = 0; i < p->nthreads; i++)
|
||||
pthread_join(p->threads[i], NULL);
|
||||
pthread_mutex_destroy(&p->lock);
|
||||
pthread_cond_destroy(&p->cond);
|
||||
free(p->buf);
|
||||
free(p->filled);
|
||||
free(p->failed);
|
||||
free(p->url);
|
||||
free(p->ua);
|
||||
free(p->ref);
|
||||
free(p);
|
||||
}
|
||||
|
||||
static parallel *par_start(stream_t *access, uint64_t size, long serial)
|
||||
{
|
||||
parallel *p = calloc(1, sizeof(*p));
|
||||
if (!p)
|
||||
return NULL;
|
||||
p->size = size;
|
||||
p->chunks = (unsigned)((size + PAR_CHUNK - 1) / PAR_CHUNK);
|
||||
p->buf = malloc(size);
|
||||
p->filled = calloc(p->chunks, sizeof(*p->filled));
|
||||
p->failed = calloc(p->chunks, sizeof(*p->failed));
|
||||
p->url = strdup(access->psz_url);
|
||||
p->ua = var_InheritString(access, "http-user-agent");
|
||||
p->ref = var_InheritString(access, "http-referrer");
|
||||
p->serial = serial;
|
||||
for (int w = 0; w < PAR_WORKERS; w++)
|
||||
p->reqs[w] = -1;
|
||||
pthread_mutex_init(&p->lock, NULL);
|
||||
pthread_cond_init(&p->cond, NULL);
|
||||
if (!p->buf || !p->filled || !p->failed || !p->url) {
|
||||
par_free(p);
|
||||
return NULL;
|
||||
}
|
||||
int workers = p->chunks < PAR_WORKERS ? (int)p->chunks : PAR_WORKERS;
|
||||
for (int w = 0; w < workers; w++) {
|
||||
struct par_worker *pw = malloc(sizeof(*pw));
|
||||
if (!pw)
|
||||
break;
|
||||
pw->p = p;
|
||||
pw->w = w;
|
||||
if (pthread_create(&p->threads[p->nthreads], NULL, par_run, pw) != 0) {
|
||||
free(pw);
|
||||
break;
|
||||
}
|
||||
p->nthreads++;
|
||||
}
|
||||
if (!p->nthreads) {
|
||||
par_free(p);
|
||||
return NULL;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
/* VLC's read from the pieces: what's in at `pos`, waiting for it if needed. */
|
||||
static ssize_t par_read(stream_t *access, ps5http_sys *sys, void *buf, size_t len)
|
||||
{
|
||||
parallel *p = sys->par;
|
||||
if (sys->pos >= p->size)
|
||||
return 0;
|
||||
unsigned idx = (unsigned)(sys->pos / PAR_CHUNK);
|
||||
uint64_t within = sys->pos % PAR_CHUNK;
|
||||
pthread_mutex_lock(&p->lock);
|
||||
while (p->filled[idx] <= within && !p->failed[idx] && !p->stop) {
|
||||
pthread_mutex_unlock(&p->lock);
|
||||
if (vlc_killed() || cancelled(sys))
|
||||
return 0;
|
||||
pthread_mutex_lock(&p->lock);
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_REALTIME, &ts);
|
||||
ts.tv_nsec += 50 * 1000000;
|
||||
if (ts.tv_nsec >= 1000000000) {
|
||||
ts.tv_sec++;
|
||||
ts.tv_nsec -= 1000000000;
|
||||
}
|
||||
if (p->filled[idx] <= within && !p->failed[idx])
|
||||
pthread_cond_timedwait(&p->cond, &p->lock, &ts);
|
||||
}
|
||||
size_t have = p->filled[idx] > within ? (size_t)(p->filled[idx] - within) : 0;
|
||||
bool failed = !have && p->failed[idx];
|
||||
pthread_mutex_unlock(&p->lock);
|
||||
if (failed) {
|
||||
PLOG("a piece at %" PRIu64 " couldn't be fetched", sys->pos);
|
||||
return 0;
|
||||
}
|
||||
if (len > have)
|
||||
len = have;
|
||||
memcpy(buf, p->buf + sys->pos, len);
|
||||
sys->pos += len;
|
||||
sys->read_bytes += len;
|
||||
(void)access;
|
||||
return (ssize_t)len;
|
||||
}
|
||||
|
||||
/* Sends a GET from `offset`; fills status, size, type, redirect. */
|
||||
static int send_request(stream_t *access, uint64_t offset)
|
||||
{
|
||||
@@ -156,17 +538,26 @@ static int send_request(stream_t *access, uint64_t offset)
|
||||
sys->redirect = NULL;
|
||||
sys->eof = false;
|
||||
|
||||
sys->conn = sceHttpCreateConnectionWithURL(http_template, access->psz_url, 1);
|
||||
char *host_header = NULL;
|
||||
char *direct = direct_url(access->psz_url, &host_header);
|
||||
const char *url = direct ? direct : access->psz_url;
|
||||
sys->conn = sceHttpCreateConnectionWithURL(http_template, url, 1);
|
||||
if (sys->conn < 0) {
|
||||
PLOG("connection failed (%#x)", (unsigned)sys->conn);
|
||||
free(direct);
|
||||
free(host_header);
|
||||
return VLC_EGENERIC;
|
||||
}
|
||||
sys->req = sceHttpCreateRequestWithURL(sys->conn, METHOD_GET, access->psz_url, 0);
|
||||
sys->req = sceHttpCreateRequestWithURL(sys->conn, METHOD_GET, url, 0);
|
||||
free(direct);
|
||||
if (sys->req < 0) {
|
||||
PLOG("request failed (%#x)", (unsigned)sys->req);
|
||||
free(host_header);
|
||||
drop_request(sys);
|
||||
return VLC_EGENERIC;
|
||||
}
|
||||
if (host_header)
|
||||
sceHttpAddRequestHeader(sys->req, "Host", host_header, HEADER_OVERWRITE);
|
||||
char *ua = var_InheritString(access, "http-user-agent");
|
||||
char *ref = var_InheritString(access, "http-referrer");
|
||||
if (ua && *ua)
|
||||
@@ -176,6 +567,11 @@ static int send_request(stream_t *access, uint64_t offset)
|
||||
free(ua);
|
||||
free(ref);
|
||||
sceHttpAddRequestHeader(sys->req, "Accept", "*/*", HEADER_OVERWRITE);
|
||||
/* never a cached copy: a live playlist read again came back the same
|
||||
* (BBC, 2026-10-13: 20 s on, the same ten segments), VLC ran out of
|
||||
* segments and the channel stopped */
|
||||
sceHttpAddRequestHeader(sys->req, "Cache-Control", "no-cache", HEADER_OVERWRITE);
|
||||
sceHttpAddRequestHeader(sys->req, "Pragma", "no-cache", HEADER_OVERWRITE);
|
||||
if (offset > 0) {
|
||||
char range[48];
|
||||
snprintf(range, sizeof(range), "bytes=%" PRIu64 "-", offset);
|
||||
@@ -190,16 +586,57 @@ static int send_request(stream_t *access, uint64_t offset)
|
||||
* certificate, its ciphers). VLC's own http takes this link; the
|
||||
* service stays for the others. Anything else: the service itself. */
|
||||
bool ssl = ((unsigned)r & 0xFFFF0000u) == 0x80950000u;
|
||||
/* 0x8043xxxx: this server's connection (down, refused, timed out):
|
||||
* this link only too. Taken as the service's, one dead channel sent
|
||||
* every stream after it to VLC's own http (2026-10-13). */
|
||||
bool net = ((unsigned)r & 0xFFFF0000u) == 0x80430000u ||
|
||||
((unsigned)r & 0xFFFF0000u) == 0x80410000u; /* 0x8041: the network's errno */
|
||||
/* stopped on purpose (the player left, a search was typed over):
|
||||
* nothing wrong with anything */
|
||||
bool stopped = vlc_killed() || cancelled(sys);
|
||||
int in_row = stopped || ssl || net ? 0 : atomic_fetch_add(&service_failures, 1) + 1;
|
||||
PLOG("sending failed (%#x): %s", (unsigned)r,
|
||||
ssl ? "SSL refused this server, VLC's own http takes this link" : "VLC's own http is used from now on");
|
||||
if (!ssl)
|
||||
stopped ? "stopped"
|
||||
: ssl ? "SSL refused this server, VLC's own http takes this link"
|
||||
: net ? "connection failed, VLC's own http tries this link"
|
||||
: in_row >= 3 ? "third time in a row: VLC's own http is used from now on"
|
||||
: "VLC's own http takes this link");
|
||||
if (in_row >= 3)
|
||||
service_refused = true;
|
||||
if (ssl)
|
||||
refuse_host(access->psz_url, true);
|
||||
/* https by name refused (the console's DNS): this one's https links
|
||||
* straight to VLC's own http from now on, no failed try first */
|
||||
if (net && !strncasecmp(access->psz_url, "https://", 8) &&
|
||||
((unsigned)r & 0xFFFF0000u) == 0x80410000u && atomic_fetch_add(&https_name_refusals, 1) + 1 == 3) {
|
||||
vlc_mutex_lock(&refused_lock);
|
||||
https_refused = true;
|
||||
vlc_mutex_unlock(&refused_lock);
|
||||
PLOG("https refused 3 times by name (the console's DNS?): VLC's own http takes every https link");
|
||||
}
|
||||
free(host_header);
|
||||
drop_request(sys);
|
||||
return VLC_EGENERIC;
|
||||
}
|
||||
atomic_store(&service_failures, 0);
|
||||
int status = 0;
|
||||
sceHttpGetStatusCode(sys->req, &status);
|
||||
sys->status = status;
|
||||
if (host_header && (status == 400 || status == 403 || status == 404 || status == 421)) {
|
||||
/* the server wanted its name, not just its address (the Host header
|
||||
* didn't get through): no more address-only requests this session */
|
||||
PLOG("HTTP %d to %s by address: VLC's own http takes it, no more by address", status, host_header);
|
||||
atomic_store(&direct_broken, true);
|
||||
free(host_header);
|
||||
drop_request(sys);
|
||||
return VLC_EGENERIC;
|
||||
}
|
||||
if (host_header && !sys->logged_direct) {
|
||||
sys->logged_direct = true;
|
||||
PLOG("%s by the app's own name lookup", host_header);
|
||||
}
|
||||
free(host_header);
|
||||
host_header = NULL;
|
||||
|
||||
char *headers = NULL;
|
||||
size_t hlen = 0;
|
||||
@@ -226,11 +663,32 @@ static int send_request(stream_t *access, uint64_t offset)
|
||||
static ssize_t Read(stream_t *access, void *buf, size_t len)
|
||||
{
|
||||
ps5http_sys *sys = access->p_sys;
|
||||
if (sys->par)
|
||||
return par_read(access, sys, buf, len);
|
||||
if (sys->eof || sys->req < 0)
|
||||
return 0;
|
||||
if (cancelled(sys)) {
|
||||
/* the player left this video: no waiting for the rest of a slow
|
||||
* server's segment (the next video waited 10 s, 2026-10-18) */
|
||||
if (!sys->cancel_logged)
|
||||
PLOG("stopped by the player: connection ended");
|
||||
sys->cancel_logged = true;
|
||||
sys->eof = true;
|
||||
return 0;
|
||||
}
|
||||
/* A little at a time: VLC's prefetch asks for 16 MiB at the start, and a
|
||||
* live channel (no size, 4-8 Mbit/s) took 15-33 s to fill that before the
|
||||
* first picture (issue #11's log). */
|
||||
size_t most = sys->size && sys->can_seek ? ((size_t)1 << 20) : ((size_t)64 << 10);
|
||||
if (len > most)
|
||||
len = most;
|
||||
vlc_interrupt_register(abort_on_interrupt, &sys->req);
|
||||
mtime_t t0 = mdate();
|
||||
int n = sceHttpReadData(sys->req, buf, len);
|
||||
sys->read_wait += mdate() - t0;
|
||||
vlc_interrupt_unregister();
|
||||
if (n > 0)
|
||||
sys->read_bytes += (uint64_t)n;
|
||||
if (n <= 0) {
|
||||
if (n < 0)
|
||||
PLOG("read failed (%#x)", (unsigned)n);
|
||||
@@ -244,6 +702,10 @@ static ssize_t Read(stream_t *access, void *buf, size_t len)
|
||||
static int Seek(stream_t *access, uint64_t offset)
|
||||
{
|
||||
ps5http_sys *sys = access->p_sys;
|
||||
if (sys->par) {
|
||||
sys->pos = offset < sys->size ? offset : sys->size;
|
||||
return VLC_SUCCESS;
|
||||
}
|
||||
if (!sys->can_seek)
|
||||
return VLC_EGENERIC;
|
||||
if (sys->size && offset >= sys->size) {
|
||||
@@ -300,6 +762,19 @@ static void Close(vlc_object_t *obj)
|
||||
{
|
||||
stream_t *access = (stream_t *)obj;
|
||||
ps5http_sys *sys = access->p_sys;
|
||||
/* each download's speed: a live channel's segments must come faster than
|
||||
* they play (a 4 s piece in under 4 s), or it freezes */
|
||||
if (sys->read_bytes >= (256u << 10) && vlc_ps5_log_details && vlc_ps5_log_details()) {
|
||||
double secs = (mdate() - sys->opened_at) / 1e6, wait = sys->read_wait / 1e6;
|
||||
if (sys->par)
|
||||
PLOG("read %" PRIu64 " KiB in %.2f s (%.0f KiB/s; %d connections)", sys->read_bytes >> 10, secs,
|
||||
secs > 0 ? (sys->read_bytes >> 10) / secs : 0.0, sys->par->nthreads);
|
||||
else
|
||||
PLOG("read %" PRIu64 " KiB in %.2f s (%.0f KiB/s; %.2f s waiting for the network)",
|
||||
sys->read_bytes >> 10, secs, secs > 0 ? (sys->read_bytes >> 10) / secs : 0.0, wait);
|
||||
}
|
||||
par_free(sys->par);
|
||||
sys->par = NULL;
|
||||
drop_request(sys);
|
||||
free(sys->content_type);
|
||||
free(sys->redirect);
|
||||
@@ -310,7 +785,7 @@ static int Open(vlc_object_t *obj)
|
||||
{
|
||||
stream_t *access = (stream_t *)obj;
|
||||
pthread_once(&service_once, start_service);
|
||||
if (http_template < 0 || service_refused)
|
||||
if (http_template < 0 || service_refused || host_refused(access->psz_url))
|
||||
return VLC_EGENERIC; /* VLC's own http module takes over */
|
||||
|
||||
ps5http_sys *sys = calloc(1, sizeof(*sys));
|
||||
@@ -318,6 +793,13 @@ static int Open(vlc_object_t *obj)
|
||||
return VLC_ENOMEM;
|
||||
sys->conn = sys->req = -1;
|
||||
access->p_sys = sys;
|
||||
sys->serial = (long)var_InheritInteger(access, "ps5http-serial");
|
||||
sys->opened_at = mdate();
|
||||
if (cancelled(sys)) {
|
||||
free(sys);
|
||||
access->p_sys = NULL;
|
||||
return VLC_EGENERIC; /* a segment for a video already stopped */
|
||||
}
|
||||
PLOG("open %s", access->psz_url);
|
||||
|
||||
if (send_request(access, 0) != VLC_SUCCESS)
|
||||
@@ -346,6 +828,11 @@ static int Open(vlc_object_t *obj)
|
||||
PLOG("HTTP %d", sys->status);
|
||||
goto error;
|
||||
}
|
||||
if (sys->status == 200 && sys->can_seek && sys->size >= PAR_MIN && sys->size <= PAR_MAX) {
|
||||
sys->par = par_start(access, sys->size, sys->serial);
|
||||
if (sys->par)
|
||||
drop_request(sys); /* the pieces have their own connections */
|
||||
}
|
||||
access->pf_read = Read;
|
||||
access->pf_block = NULL;
|
||||
access->pf_seek = Seek;
|
||||
@@ -365,5 +852,9 @@ vlc_module_begin()
|
||||
set_subcategory(SUBCAT_INPUT_ACCESS)
|
||||
set_capability("access", 300)
|
||||
add_shortcut("http", "https")
|
||||
/* the player's number for the open (see vlc_ps5_stream_cancelled) */
|
||||
add_integer("ps5http-serial", 0, "Player open", NULL, true)
|
||||
change_private()
|
||||
change_safe()
|
||||
set_callbacks(Open, Close)
|
||||
vlc_module_end()
|
||||
+840
-63
File diff suppressed because it is too large.
Load diff
+3
-1
@@ -7,7 +7,9 @@
|
||||
# bash ps5/radv-patches.sh
|
||||
#
|
||||
# patches/mesa/0001: HDR10 output (the framebuffers switched in place, the
|
||||
# output's dynamic range read back) for src/player.cc.
|
||||
# output's dynamic range read back) for src/player.cc, and the output's
|
||||
# refresh for films (23.976 / 24 / 50 Hz) for src/main.cc. One patch per file:
|
||||
# each run starts the file again from the fork's original.
|
||||
set -euo pipefail
|
||||
here=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
|
||||
repo=$(dirname "$here")
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"conceptId": "85300",
|
||||
"contentBadgeType": 1,
|
||||
"contentId": "UP9000-PPSA85300_00-VLCFORPS50000001",
|
||||
"contentVersion": "01.000.005",
|
||||
"contentVersion": "01.000.006",
|
||||
"downloadDataSize": 256,
|
||||
"gameIntent": {
|
||||
"permittedIntents": [
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"conceptId": "85300",
|
||||
"contentBadgeType": 2,
|
||||
"contentId": "UP9000-PPSA85300_00-VLCFORPS50000001",
|
||||
"contentVersion": "01.000.005",
|
||||
"contentVersion": "01.000.006",
|
||||
"downloadDataSize": 256,
|
||||
"localizedParameters": {
|
||||
"defaultLanguage": "en-US",
|
||||
|
||||
+38
-1
@@ -28,6 +28,7 @@
|
||||
/* The console's Vulkan driver, with patches/mesa/0001 (absent elsewhere). */
|
||||
extern "C" int vlcps5_wsi_videoout_set_hdr(int enable) __attribute__((weak));
|
||||
extern "C" int vlcps5_wsi_videoout_dynamic_range(void) __attribute__((weak));
|
||||
extern "C" int vlcps5_wsi_videoout_set_refresh(int mode, int *period_us) __attribute__((weak));
|
||||
|
||||
Gfx gfx;
|
||||
|
||||
@@ -617,6 +618,23 @@ bool gfx_set_hdr(bool on)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool gfx_set_refresh(int mode, int *period_us)
|
||||
{
|
||||
*period_us = 0;
|
||||
if (!vlcps5_wsi_videoout_set_refresh)
|
||||
return false;
|
||||
/* nothing in flight while the output changes */
|
||||
vkDeviceWaitIdle(gfx.device);
|
||||
return vlcps5_wsi_videoout_set_refresh(mode, period_us) == 0;
|
||||
}
|
||||
|
||||
int gfx_output_range()
|
||||
{
|
||||
if (vlcps5_wsi_videoout_dynamic_range)
|
||||
return vlcps5_wsi_videoout_dynamic_range();
|
||||
return gfx.offscreen && getenv("VLCPS5_HDR_TEST") ? atoi(getenv("VLCPS5_HDR_TEST")) : -1;
|
||||
}
|
||||
|
||||
void gfx_begin_ui_pass(VkCommandBuffer cmd)
|
||||
{
|
||||
begin(cmd, hdr.ui_pass, hdr.ui_fb, 0.0f); /* clear: transparent */
|
||||
@@ -817,7 +835,26 @@ bool gfx_image(uint32_t w, uint32_t h, VkFormat format, VkImageUsageFlags usage,
|
||||
ai.memoryTypeIndex = gfx_memory_type(req.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
||||
if (ai.memoryTypeIndex == UINT32_MAX)
|
||||
ai.memoryTypeIndex = gfx_memory_type(req.memoryTypeBits, 0);
|
||||
CHECK(vkAllocateMemory(gfx.device, &ai, nullptr, memory));
|
||||
VkResult r = vkAllocateMemory(gfx.device, &ai, nullptr, memory);
|
||||
/* The video memory full (a 4K HDR film's decoder holds 840 MiB of it):
|
||||
* any other kind the image can live in. Failing, a 4K video stayed
|
||||
* black for the whole of its first open (2026-10-18). */
|
||||
for (uint32_t t = 0; r != VK_SUCCESS && t < 32; t++) {
|
||||
if (!(req.memoryTypeBits & (1u << t)) || t == ai.memoryTypeIndex)
|
||||
continue;
|
||||
VkMemoryAllocateInfo other = ai;
|
||||
other.memoryTypeIndex = t;
|
||||
r = vkAllocateMemory(gfx.device, &other, nullptr, memory);
|
||||
if (r == VK_SUCCESS)
|
||||
fprintf(stderr, "gfx: %ux%u image: video memory full, memory type %u instead\n", w, h, t);
|
||||
}
|
||||
if (r != VK_SUCCESS) {
|
||||
fprintf(stderr, "gfx: no memory for a %ux%u image (%llu KiB): %d\n", w, h,
|
||||
(unsigned long long)(req.size >> 10), r);
|
||||
vkDestroyImage(gfx.device, *image, nullptr);
|
||||
*image = VK_NULL_HANDLE;
|
||||
return false;
|
||||
}
|
||||
CHECK(vkBindImageMemory(gfx.device, *image, *memory, 0));
|
||||
VkImageViewCreateInfo vi = { VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO };
|
||||
vi.image = *image;
|
||||
|
||||
@@ -46,6 +46,11 @@ void gfx_begin_pass(VkCommandBuffer cmd);
|
||||
void gfx_end_frame(VkCommandBuffer cmd);
|
||||
/* HDR10 output on or off (waits for the GPU); false if it couldn't change. */
|
||||
bool gfx_set_hdr(bool on);
|
||||
/* What the console sends to the TV now: 1 SDR, 2 HDR, other unknown. */
|
||||
int gfx_output_range();
|
||||
/* The output's refresh (VideoOut modes: 2 = 23.976 Hz, 3 = 24, 9 = 50,
|
||||
* 1 = the default); the vblank period measured after, in microseconds. */
|
||||
bool gfx_set_refresh(int mode, int *period_us);
|
||||
/* With HDR10 output on, a frame is: the UI pass (ImGui into its own layer),
|
||||
* then the HDR pass (the video, then gfx_draw_ui_layer), then gfx_end_frame. */
|
||||
void gfx_begin_ui_pass(VkCommandBuffer cmd);
|
||||
|
||||
+58
-1
@@ -8,7 +8,9 @@
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <string>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "backends/imgui_impl_vulkan.h"
|
||||
@@ -97,13 +99,68 @@ int main(void)
|
||||
|
||||
/* HDR10 output while an HDR video shows (once a refusal, not again
|
||||
* until the wish changes) */
|
||||
/* The wish has to hold a moment first: while a video starts (its
|
||||
* picture set up, set up again) it came and went, and each switch
|
||||
* blanks the screen (on, off, on, off in a second: a 2026-10-13 log). */
|
||||
static bool hdr_refused;
|
||||
static double wish_since;
|
||||
static bool wish_last;
|
||||
bool want_hdr = video_wants_hdr_output();
|
||||
if (want_hdr != gfx.hdr && !(want_hdr && hdr_refused))
|
||||
if (want_hdr != wish_last) {
|
||||
wish_last = want_hdr;
|
||||
wish_since = plat_time();
|
||||
}
|
||||
double hold = want_hdr ? 0.4 : 1.0;
|
||||
if (want_hdr != gfx.hdr && !(want_hdr && hdr_refused) && plat_time() - wish_since >= hold)
|
||||
hdr_refused = !gfx_set_hdr(want_hdr);
|
||||
if (!want_hdr)
|
||||
hdr_refused = false;
|
||||
|
||||
/* Match frame rate: the output's refresh follows the film playing
|
||||
* (23.976 / 24 / 50 Hz), and goes back to the default after. Asked
|
||||
* once a picture is up (the rate is known then); a mode the TV
|
||||
* doesn't really run at (the vblanks say) is put back and not asked
|
||||
* again for that video. */
|
||||
{
|
||||
static int mode_now = 1;
|
||||
static std::string refused_for;
|
||||
int want = 1;
|
||||
const VideoInfo &vi = player_video_info();
|
||||
if (player_match_rate() && player_active() && video_has_picture() && vi.fps > 0 &&
|
||||
refused_for != player_current_path()) {
|
||||
float f = vi.fps;
|
||||
if (fabsf(f - 23.976f) < 0.01f)
|
||||
want = 2;
|
||||
else if (fabsf(f - 24.0f) < 0.01f)
|
||||
want = 3;
|
||||
else if (fabsf(f - 25.0f) < 0.05f || fabsf(f - 50.0f) < 0.1f)
|
||||
want = 9;
|
||||
}
|
||||
if (want != mode_now) {
|
||||
int period = 0;
|
||||
bool ok = gfx_set_refresh(want, &period);
|
||||
static const struct { int mode, us; } expect[] = { { 1, 16683 }, { 2, 41708 }, { 3, 41667 }, { 9, 20000 } };
|
||||
int us = 0;
|
||||
for (const auto &e : expect)
|
||||
if (e.mode == want)
|
||||
us = e.us;
|
||||
if (ok && period > 0 && fabs(period - us) > us * 0.05) {
|
||||
fprintf(stderr, "main: output mode %d taken but a vblank every %d us (not %d): default again\n",
|
||||
want, period, us);
|
||||
gfx_set_refresh(1, &period);
|
||||
ok = false;
|
||||
}
|
||||
if (ok || want == 1) {
|
||||
mode_now = ok ? want : 1;
|
||||
} else {
|
||||
refused_for = player_current_path();
|
||||
if (mode_now != 1 && gfx_set_refresh(1, &period))
|
||||
mode_now = 1;
|
||||
}
|
||||
fprintf(stderr, "main: refresh %s for %.3f fps\n", ok ? "matched" : "not matched", vi.fps);
|
||||
}
|
||||
}
|
||||
|
||||
VkCommandBuffer cmd = gfx_begin_frame();
|
||||
if (!cmd)
|
||||
continue;
|
||||
|
||||
+249
@@ -15,6 +15,7 @@
|
||||
|
||||
#include <vlc/vlc.h>
|
||||
|
||||
#include "osub.h"
|
||||
#include "platform.h"
|
||||
#include "player.h"
|
||||
|
||||
@@ -391,6 +392,199 @@ bool list_menu(const std::string &url)
|
||||
return true;
|
||||
}
|
||||
|
||||
/* ---- search: every free TV channel and radio stations by name, fetched in
|
||||
* the background while Search is typed into (not through Browse) ---- */
|
||||
|
||||
struct Fetch {
|
||||
libvlc_media_t *m = nullptr;
|
||||
bool loading = false;
|
||||
std::string url;
|
||||
std::vector<NetEntry> got;
|
||||
};
|
||||
Fetch radio_found;
|
||||
int search_serial; /* changes when search results arrive */
|
||||
|
||||
/* Every free TV channel for Search: index.m3u downloaded and read here, as
|
||||
* names, addresses and options only. Kept as VLC media (11,000 of them, each
|
||||
* with its locks), it and the same list open in Browse ran the console out
|
||||
* of mutexes: VLC aborts when it can't make one (crash, 2026-10-13). */
|
||||
std::vector<NetEntry> tv_list; /* main thread */
|
||||
std::mutex tv_lock;
|
||||
std::vector<NetEntry> tv_arrived; /* from the download thread */
|
||||
bool tv_has_arrived, tv_loading, tv_started;
|
||||
std::thread tv_thread;
|
||||
|
||||
std::vector<NetEntry> parse_m3u(const std::string &text)
|
||||
{
|
||||
std::vector<NetEntry> out;
|
||||
std::string name;
|
||||
std::vector<std::string> opts;
|
||||
size_t at = 0;
|
||||
while (at < text.size()) {
|
||||
size_t eol = text.find('\n', at);
|
||||
if (eol == std::string::npos)
|
||||
eol = text.size();
|
||||
std::string line = text.substr(at, eol - at);
|
||||
at = eol + 1;
|
||||
while (!line.empty() && (line.back() == '\r' || line.back() == ' '))
|
||||
line.pop_back();
|
||||
if (line.empty())
|
||||
continue;
|
||||
if (line.compare(0, 8, "#EXTINF:") == 0) {
|
||||
/* the name: after the first comma outside the attributes' quotes */
|
||||
bool quoted = false;
|
||||
size_t comma = std::string::npos;
|
||||
for (size_t i = 8; i < line.size(); i++) {
|
||||
if (line[i] == '"')
|
||||
quoted = !quoted;
|
||||
else if (line[i] == ',' && !quoted) {
|
||||
comma = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
name = comma == std::string::npos ? "" : line.substr(comma + 1);
|
||||
while (!name.empty() && name[0] == ' ')
|
||||
name.erase(0, 1);
|
||||
} else if (line.compare(0, 11, "#EXTVLCOPT:") == 0) {
|
||||
opts.push_back(":" + line.substr(11));
|
||||
} else if (line[0] != '#') {
|
||||
NetEntry e;
|
||||
e.url = line;
|
||||
e.name = name.empty() ? line : name;
|
||||
e.dir = false;
|
||||
e.options.swap(opts);
|
||||
out.push_back(std::move(e));
|
||||
name.clear();
|
||||
opts.clear();
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
void tv_start_download()
|
||||
{
|
||||
if (tv_started)
|
||||
return;
|
||||
tv_started = true;
|
||||
tv_loading = true;
|
||||
search_serial++;
|
||||
std::string url = std::string(TV_LISTS) + "index.m3u";
|
||||
fprintf(stderr, "net: search fetching %s\n", url.c_str());
|
||||
tv_thread = std::thread([url] {
|
||||
std::string body, error;
|
||||
std::vector<NetEntry> got;
|
||||
if (web_https_get(url, body, error))
|
||||
got = parse_m3u(body);
|
||||
fprintf(stderr, "net: search got %zu channels (%zu bytes)%s%s\n", got.size(), body.size(),
|
||||
error.empty() ? "" : ": ", error.c_str());
|
||||
std::lock_guard<std::mutex> g(tv_lock);
|
||||
tv_arrived.swap(got);
|
||||
tv_has_arrived = true;
|
||||
});
|
||||
}
|
||||
std::string search_query, radio_query; /* typed; last asked of radio-browser */
|
||||
double search_typed_at;
|
||||
|
||||
void fetch_clear(Fetch &f)
|
||||
{
|
||||
if (f.m) {
|
||||
if (f.loading)
|
||||
libvlc_media_parse_stop(f.m);
|
||||
libvlc_media_release(f.m);
|
||||
f.m = nullptr;
|
||||
}
|
||||
f.loading = false;
|
||||
for (NetEntry &e : f.got)
|
||||
if (e.media)
|
||||
libvlc_media_release(e.media);
|
||||
f.got.clear();
|
||||
}
|
||||
|
||||
void fetch_start(Fetch &f, const std::string &url)
|
||||
{
|
||||
fetch_clear(f);
|
||||
libvlc_instance_t *vlc = player_vlc();
|
||||
f.url = url;
|
||||
f.m = vlc ? libvlc_media_new_location(vlc, url.c_str()) : nullptr;
|
||||
if (f.m && libvlc_media_parse_with_options(f.m, libvlc_media_parse_network, 20000) == 0) {
|
||||
f.loading = true;
|
||||
fprintf(stderr, "net: search fetching %s\n", url.c_str());
|
||||
}
|
||||
search_serial++;
|
||||
}
|
||||
|
||||
void fetch_poll(Fetch &f)
|
||||
{
|
||||
if (!f.loading || libvlc_media_get_parsed_status(f.m) == 0)
|
||||
return;
|
||||
f.loading = false;
|
||||
if (libvlc_media_list_t *subs = libvlc_media_subitems(f.m)) {
|
||||
libvlc_media_list_lock(subs);
|
||||
for (int i = 0; i < libvlc_media_list_count(subs); i++) {
|
||||
libvlc_media_t *m = libvlc_media_list_item_at_index(subs, i);
|
||||
if (!m)
|
||||
continue;
|
||||
char *mrl = libvlc_media_get_mrl(m), *title = libvlc_media_get_meta(m, libvlc_meta_Title);
|
||||
if (mrl && libvlc_media_get_type(m) != libvlc_media_type_directory) {
|
||||
NetEntry e;
|
||||
e.url = mrl;
|
||||
e.name = title ? title : e.url.substr(e.url.rfind('/') + 1);
|
||||
e.dir = false;
|
||||
e.media = m;
|
||||
libvlc_media_retain(m);
|
||||
f.got.push_back(e);
|
||||
}
|
||||
free(mrl);
|
||||
free(title);
|
||||
libvlc_media_release(m);
|
||||
}
|
||||
libvlc_media_list_unlock(subs);
|
||||
libvlc_media_list_release(subs);
|
||||
}
|
||||
fprintf(stderr, "net: search got %zu from %s\n", f.got.size(), f.url.c_str());
|
||||
search_serial++;
|
||||
}
|
||||
|
||||
std::string url_encode(const std::string &s)
|
||||
{
|
||||
std::string out;
|
||||
for (unsigned char c : s) {
|
||||
if (isalnum(c) || c == '-' || c == '.' || c == '_') {
|
||||
out += (char)c;
|
||||
} else {
|
||||
char h[4];
|
||||
snprintf(h, sizeof(h), "%%%02X", c);
|
||||
out += h;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
void search_update()
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> g(tv_lock);
|
||||
if (tv_has_arrived) {
|
||||
tv_has_arrived = false;
|
||||
tv_list.swap(tv_arrived);
|
||||
tv_loading = false;
|
||||
if (tv_list.empty())
|
||||
tv_started = false; /* failed: asked again the next time Search is used */
|
||||
search_serial++;
|
||||
}
|
||||
}
|
||||
if (!tv_loading && tv_thread.joinable())
|
||||
tv_thread.join();
|
||||
fetch_poll(radio_found);
|
||||
/* radio-browser is asked once the typing pauses */
|
||||
if (search_query.size() >= 2 && search_query != radio_query && plat_time() - search_typed_at > 0.6) {
|
||||
radio_query = search_query;
|
||||
fetch_start(radio_found, std::string(RADIO_LISTS) + "byname/" + url_encode(search_query) +
|
||||
"?limit=40&order=clickcount&reverse=true&hidebroken=true");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // namespace
|
||||
|
||||
void net_init()
|
||||
@@ -430,6 +624,10 @@ void net_shutdown()
|
||||
upnp_starter.join();
|
||||
net_list_stop();
|
||||
clear_entries();
|
||||
if (tv_thread.joinable())
|
||||
tv_thread.join();
|
||||
tv_list.clear();
|
||||
fetch_clear(radio_found);
|
||||
if (upnp) {
|
||||
libvlc_media_discoverer_stop(upnp);
|
||||
libvlc_media_discoverer_release(upnp);
|
||||
@@ -455,6 +653,7 @@ void net_update()
|
||||
libvlc_dialog_dismiss(id);
|
||||
if (list_state == NET_LOADING && libvlc_media_get_parsed_status(listing) != 0)
|
||||
finish_listing();
|
||||
search_update();
|
||||
}
|
||||
|
||||
std::vector<NetServer> net_servers()
|
||||
@@ -497,6 +696,56 @@ void net_forget_server(const std::string &url)
|
||||
store_saved();
|
||||
}
|
||||
|
||||
|
||||
void net_search_typed(const std::string &query)
|
||||
{
|
||||
std::string q = query;
|
||||
while (!q.empty() && q.back() == ' ')
|
||||
q.pop_back();
|
||||
while (!q.empty() && q[0] == ' ')
|
||||
q.erase(0, 1);
|
||||
tv_start_download(); /* once a session (again if it failed) */
|
||||
if (q != search_query) {
|
||||
search_query = q;
|
||||
search_typed_at = plat_time();
|
||||
if (q.size() < 2) {
|
||||
fetch_clear(radio_found);
|
||||
radio_query.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool net_search_busy()
|
||||
{
|
||||
return tv_loading || radio_found.loading ||
|
||||
(search_query.size() >= 2 && search_query != radio_query);
|
||||
}
|
||||
|
||||
int net_search_serial()
|
||||
{
|
||||
return search_serial;
|
||||
}
|
||||
|
||||
const std::vector<NetEntry> &net_search_tv()
|
||||
{
|
||||
return tv_list;
|
||||
}
|
||||
|
||||
const std::vector<NetEntry> &net_search_radio()
|
||||
{
|
||||
return radio_found.got;
|
||||
}
|
||||
|
||||
libvlc_media_t *net_search_media(const std::string &url)
|
||||
{
|
||||
for (NetEntry &e : radio_found.got)
|
||||
if (e.url == url && e.media) {
|
||||
libvlc_media_retain(e.media);
|
||||
return e.media;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool net_is_saved_server(const std::string &url)
|
||||
{
|
||||
for (const Saved &s : saved)
|
||||
|
||||
@@ -23,6 +23,7 @@ struct NetEntry {
|
||||
std::string url; /* no login in it: net_login_options() gives it */
|
||||
bool dir;
|
||||
libvlc_media_t *media = nullptr; /* VLC's own item (keeps a list's #EXTVLCOPT) */
|
||||
std::vector<std::string> options; /* or a list's #EXTVLCOPT as media options (":http-referrer=...") */
|
||||
};
|
||||
|
||||
enum NetListState { NET_IDLE, NET_LOADING, NET_READY, NET_FAILED };
|
||||
@@ -60,6 +61,15 @@ libvlc_media_t *net_take_media(const std::string &url);
|
||||
/* The media options that log in to an address's saved share (":smb-user=..."). */
|
||||
std::vector<std::string> net_login_options(const std::string &url);
|
||||
|
||||
/* Search, as it's typed: every free TV channel (the whole list, fetched once a
|
||||
* session) and radio stations by name (asked once the typing pauses). */
|
||||
void net_search_typed(const std::string &query);
|
||||
bool net_search_busy();
|
||||
int net_search_serial(); /* changes when results arrive */
|
||||
const std::vector<NetEntry> &net_search_tv();
|
||||
const std::vector<NetEntry> &net_search_radio();
|
||||
libvlc_media_t *net_search_media(const std::string &url); /* with a reference, or null */
|
||||
|
||||
/* What VLC is asking (login / question), answered from the interface. */
|
||||
NetAsk net_ask();
|
||||
void net_answer_login(const std::string &user, const std::string &password, bool remember);
|
||||
|
||||
+16
@@ -584,6 +584,22 @@ bool start()
|
||||
|
||||
} // namespace
|
||||
|
||||
/* A plain HTTPS download (redirects followed), for other parts of the app. */
|
||||
bool web_https_get(const std::string &url, std::string &body, std::string &error)
|
||||
{
|
||||
Reply r = https_get(url);
|
||||
if (!r.error.empty()) {
|
||||
error = r.error;
|
||||
return false;
|
||||
}
|
||||
if (r.status != 200) {
|
||||
error = "HTTP " + std::to_string(r.status);
|
||||
return false;
|
||||
}
|
||||
body.swap(r.body);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool osub_has_key()
|
||||
{
|
||||
return !pref_str("osub_key", "").empty();
|
||||
|
||||
@@ -34,3 +34,6 @@ OsubState osub_state();
|
||||
std::vector<SubResult> osub_results();
|
||||
std::string osub_message(); /* what went wrong, or "12 downloads left today" */
|
||||
std::string osub_saved_path(); /* the file a download wrote, once */
|
||||
|
||||
/* A plain HTTPS GET (redirects followed; blocking: a background thread's). */
|
||||
bool web_https_get(const std::string &url, std::string &body, std::string &error);
|
||||
+1
-1
@@ -23,7 +23,7 @@ extern "C" {
|
||||
#endif
|
||||
|
||||
#define VLC_PS5_TITLE_ID "PPSA85300"
|
||||
#define VLC_PS5_VERSION "0.4"
|
||||
#define VLC_PS5_VERSION "0.4.5"
|
||||
|
||||
/* DualSense buttons (scePadRead's layout, as QUICK3 and PS5_vkQuake use it),
|
||||
* plus the triggers past half as buttons. */
|
||||
|
||||
+20
-1
@@ -10,6 +10,7 @@
|
||||
* options touchpad)
|
||||
* <frame> hold <button> <frames>
|
||||
* <frame> shot <file.png> saves that frame
|
||||
* <frame> type <text> the on-screen keyboard gets it (_ for a space)
|
||||
* <frame> quit
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
@@ -27,7 +28,7 @@
|
||||
|
||||
typedef struct {
|
||||
uint64_t frame;
|
||||
enum { STEP_HOLD, STEP_SHOT, STEP_QUIT, STEP_STICK } kind;
|
||||
enum { STEP_HOLD, STEP_SHOT, STEP_QUIT, STEP_STICK, STEP_TYPE } kind;
|
||||
uint32_t button;
|
||||
float rx, ry; /* STEP_STICK: the right stick, held for frames */
|
||||
uint64_t frames;
|
||||
@@ -87,6 +88,11 @@ static void load_script(void)
|
||||
s->frames = frames;
|
||||
} else if (!strcmp(word, "shot") && sscanf(line, "%*u %*s %255s", s->path) == 1) {
|
||||
s->kind = STEP_SHOT;
|
||||
} else if (!strcmp(word, "type") && sscanf(line, "%*u %*s %255s", s->path) == 1) {
|
||||
s->kind = STEP_TYPE;
|
||||
for (char *c = s->path; *c; c++)
|
||||
if (*c == '_')
|
||||
*c = ' ';
|
||||
} else if (!strcmp(word, "quit")) {
|
||||
s->kind = STEP_QUIT;
|
||||
quit_frame = frame;
|
||||
@@ -255,3 +261,16 @@ bool plat_script_done(uint64_t frame)
|
||||
{
|
||||
return frame >= quit_frame;
|
||||
}
|
||||
|
||||
/* Script "type" steps due by now, once (the interface's keyboard reads it). */
|
||||
const char *plat_take_typed(void)
|
||||
{
|
||||
static int next;
|
||||
for (; next < nsteps; next++) {
|
||||
if (steps[next].frame > frame_now)
|
||||
break;
|
||||
if (steps[next].kind == STEP_TYPE)
|
||||
return steps[next++].path;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
@@ -166,6 +166,7 @@ static int boot_setting(const char *key, int fallback)
|
||||
* hw_decode=0 not at all. */
|
||||
int sceSysmoduleLoadModule(uint16_t id);
|
||||
void vlc_ps5_vdec_prepare(void) __attribute__((weak)); /* ps5/modules/ps5vdec.c */
|
||||
void vlc_ps5_vdec_probe(const char *when) __attribute__((weak));
|
||||
#define SYSMODULE_VIDEODEC2 207
|
||||
static bool vdec_ready;
|
||||
static double jb_wait = 2.5; /* seconds the Helper has to answer */
|
||||
@@ -193,6 +194,8 @@ static void load_video_decoder(void)
|
||||
/* its compute queue now too, while still in the sandbox */
|
||||
if (vdec_ready && vlc_ps5_vdec_prepare)
|
||||
vlc_ps5_vdec_prepare();
|
||||
if (vdec_ready && vlc_ps5_vdec_probe)
|
||||
vlc_ps5_vdec_probe(plat_full_access() ? "after full access" : "before full access");
|
||||
}
|
||||
|
||||
int vlc_ps5_vdec_available(void)
|
||||
@@ -527,6 +530,8 @@ void plat_init(void)
|
||||
request_full_access();
|
||||
if (hw_decode && vdec_load != 1)
|
||||
load_video_decoder();
|
||||
else if (vdec_ready && vlc_ps5_vdec_probe && plat_full_access())
|
||||
vlc_ps5_vdec_probe("after full access");
|
||||
use_app_dir();
|
||||
}
|
||||
|
||||
|
||||
+448
-63
@@ -54,6 +54,10 @@
|
||||
|
||||
namespace {
|
||||
|
||||
extern bool log_debug;
|
||||
/* The picture diagnostics (2026-10-18): only with debug=1 in settings.txt */
|
||||
#define DIAG(...) do { if (log_debug) fprintf(stderr, __VA_ARGS__); } while (0)
|
||||
|
||||
libvlc_instance_t *vlc;
|
||||
libvlc_media_player_t *mp;
|
||||
std::string current_path;
|
||||
@@ -76,6 +80,7 @@ std::atomic<bool> vdec_failed{ false };
|
||||
std::atomic<int> vdec_active{ 0 }; /* decoders open on the console's decoder */
|
||||
/* how long a video takes to show its first picture (the log) */
|
||||
double opened_at;
|
||||
int64_t opened_from_ms; /* where the open started */
|
||||
bool first_picture_wait;
|
||||
std::string software_path;
|
||||
struct Health {
|
||||
@@ -149,6 +154,9 @@ struct Colour {
|
||||
bool wide = false; /* BT.2020 primaries */
|
||||
float peak = 1000; /* the picture's brightest, in nits (HDR) */
|
||||
bool full_range = false; /* 0-255 (VLC says so; NV12 has no "J" name) */
|
||||
/* the decoder's buffer and the part of it that is picture (1280x720 in
|
||||
* 1280x736): patches/0018 has vmem keep it, the shader crops */
|
||||
uint32_t buf_w = 0, buf_h = 0, vis_w = 0, vis_h = 0;
|
||||
};
|
||||
/* HDR's reference white (BT.2408): what shows as the TV's white in SDR */
|
||||
const float SDR_WHITE = 203;
|
||||
@@ -163,6 +171,7 @@ struct Format {
|
||||
* interleaved in one plane; P010 keeps its 10 bits at the top */
|
||||
bool two_planes;
|
||||
Colour colour;
|
||||
uint32_t vis_w, vis_h; /* the picture in the buffer (top left) */
|
||||
};
|
||||
|
||||
std::mutex pic_lock;
|
||||
@@ -170,6 +179,54 @@ std::condition_variable pic_freed;
|
||||
Slot slots[SLOTS];
|
||||
Format format;
|
||||
int generation; /* bumped by every vmem setup */
|
||||
std::atomic<bool> stopping; /* the video before is stopping (in the background) */
|
||||
/* The same video reopened (hardware to software, fast decoding): its last
|
||||
* picture stays up until the reopened one's first comes, instead of black */
|
||||
std::atomic<bool> hold_picture;
|
||||
|
||||
/* Each open's number, given to ps5http (":ps5http-serial"): a stop marks its
|
||||
* number cancelled and its connections end at once. Leaving a slow channel
|
||||
* waited 10 s for its server to finish sending a segment, and the next video
|
||||
* waited with it (2026-10-18). */
|
||||
std::atomic<long> cancelled_serial;
|
||||
long next_serial, queued_serial, started_serial;
|
||||
|
||||
/* A live stream that ended by itself (a segment the server lost, 404):
|
||||
* opened again, a few times, instead of "Watch again" on a TV channel. */
|
||||
int live_reconnects;
|
||||
bool live_reconnecting;
|
||||
int64_t live_last_ms, live_last_len;
|
||||
|
||||
/* Diagnostics, always in the log, a few lines per video (2026-10-18: a
|
||||
* flash of another video with hardware decoding, not seen on the PC): each
|
||||
* of the first pictures' brightness when VLC hands it over and when it goes
|
||||
* up to the screen (a buffer written over in between shows as two numbers),
|
||||
* and every unusual path a picture takes. */
|
||||
const int DIAG_PICTURES = 15;
|
||||
int diag_shown, diag_up, diag_draws, diag_odd;
|
||||
std::atomic<int> diag_vlc_timing; /* VLC's lateness messages let into the log, this video */
|
||||
static const char *const slot_names[] = { "free", "writing", "ready", "shown", "gpu" };
|
||||
|
||||
/* Average brightness (0-255) of a slot's picture: 16x16 points of its luma. */
|
||||
unsigned slot_luma(const Slot &sl, const Format &f)
|
||||
{
|
||||
if (!sl.mapped || !f.width || !f.height)
|
||||
return 0;
|
||||
uint32_t w = f.vis_w ? f.vis_w : f.width, h = f.vis_h ? f.vis_h : f.height;
|
||||
const uint8_t *y0 = sl.mapped + f.offsets[0];
|
||||
uint64_t sum = 0;
|
||||
for (unsigned y = 0; y < 16; y++) {
|
||||
const uint8_t *row = y0 + (size_t)(h * y / 16 + h / 32) * f.pitches[0];
|
||||
for (unsigned x = 0; x < 16; x++) {
|
||||
unsigned px = w * x / 16 + w / 32;
|
||||
if (!f.ten_bit)
|
||||
sum += row[px];
|
||||
else /* P010: 10 bits at the top; I0AL: at the bottom */
|
||||
sum += f.two_planes ? ((const uint16_t *)row)[px] >> 8 : ((const uint16_t *)row)[px] >> 2;
|
||||
}
|
||||
}
|
||||
return (unsigned)(sum / 256);
|
||||
}
|
||||
Colour next_colour; /* vmem_colour's, for the setup right after it */
|
||||
int shown = -1; /* the slot VLC displayed last, not uploaded yet */
|
||||
std::atomic<uint64_t> completed_frame{ 0 };
|
||||
@@ -189,9 +246,14 @@ struct Planes {
|
||||
uint32_t w[PLANES], h[PLANES];
|
||||
int generation = -1;
|
||||
bool has_picture;
|
||||
/* the frame textures of a new shape came in: the interface placed the
|
||||
* video earlier in that frame, with the shape before */
|
||||
uint64_t new_shape_frame;
|
||||
float prev_aspect; /* the last setup's picture shape */
|
||||
bool ten_bit, full_range, two_planes;
|
||||
Colour colour;
|
||||
uint32_t buf_w, buf_h;
|
||||
uint32_t vis_w, vis_h;
|
||||
} planes;
|
||||
|
||||
VkSampler sampler;
|
||||
@@ -259,6 +321,12 @@ void vmem_colour(void *opaque, const video_format_t *fmt)
|
||||
default: c.matrix = c.transfer != TRC_SDR ? MATRIX_2020 : MATRIX_AUTO; break;
|
||||
}
|
||||
c.full_range = fmt->b_color_range_full;
|
||||
if (!fmt->i_x_offset && !fmt->i_y_offset) {
|
||||
c.buf_w = fmt->i_width;
|
||||
c.buf_h = fmt->i_height;
|
||||
c.vis_w = fmt->i_visible_width;
|
||||
c.vis_h = fmt->i_visible_height;
|
||||
}
|
||||
c.wide = fmt->primaries == COLOR_PRIMARIES_BT2020 ||
|
||||
(fmt->primaries == COLOR_PRIMARIES_UNDEF && c.transfer != TRC_SDR);
|
||||
/* How bright it gets: the content's own light level, else the mastering
|
||||
@@ -386,6 +454,12 @@ unsigned vmem_setup(void **opaque, char *chroma, unsigned *width, unsigned *heig
|
||||
f.two_planes = two;
|
||||
f.colour = next_colour;
|
||||
f.full_range = full || f.colour.full_range;
|
||||
/* vmem keeps the decoder's visible part when the buffer stays the
|
||||
* decoder's size (patches/0018); otherwise all of it is picture */
|
||||
bool keep = f.colour.buf_w == *width && f.colour.buf_h == *height && f.colour.vis_w &&
|
||||
f.colour.vis_h && f.colour.vis_w <= *width && f.colour.vis_h <= *height;
|
||||
f.vis_w = keep ? f.colour.vis_w : *width;
|
||||
f.vis_h = keep ? f.colour.vis_h : *height;
|
||||
next_colour = Colour{};
|
||||
uint32_t bpp = ten ? 2 : 1;
|
||||
uint32_t cw = (*width + 1) / 2, ch = (*height + 1) / 2;
|
||||
@@ -419,6 +493,8 @@ unsigned vmem_setup(void **opaque, char *chroma, unsigned *width, unsigned *heig
|
||||
format = f;
|
||||
info_stale = true;
|
||||
generation++;
|
||||
diag_shown = diag_up = diag_draws = 0;
|
||||
diag_vlc_timing = 0;
|
||||
fprintf(stderr, "player: pictures %ux%u %s%s%s, %d x %llu KiB\n", f.width, f.height,
|
||||
ten ? "10-bit" : "8-bit", two ? (ten ? " P010" : " NV12") : "",
|
||||
f.full_range ? " full range" : "", SLOTS,
|
||||
@@ -464,9 +540,13 @@ void *vmem_lock(void *opaque, void **out)
|
||||
found = i;
|
||||
break;
|
||||
}
|
||||
if (diag_odd++ < 20)
|
||||
DIAG("diag: no free picture buffer: VLC writes over ready one %d\n", found);
|
||||
}
|
||||
if (found < 0 || !slots[found].buffer) {
|
||||
/* Shouldn't happen; give VLC somewhere harmless to write. */
|
||||
if (diag_odd++ < 20)
|
||||
DIAG("diag: no picture buffer at all: VLC writes to a scratch one\n");
|
||||
static uint8_t *scratch;
|
||||
static VkDeviceSize scratch_size;
|
||||
if (scratch_size < format.size) {
|
||||
@@ -499,15 +579,37 @@ void vmem_display(void *opaque, void *picture)
|
||||
(void)opaque;
|
||||
int i = (int)(intptr_t)picture;
|
||||
std::lock_guard<std::mutex> g(pic_lock);
|
||||
if (i < 0 || i >= SLOTS || slots[i].state != READY)
|
||||
if (i < 0 || i >= SLOTS || slots[i].state != READY) {
|
||||
if (diag_odd++ < 20)
|
||||
DIAG("diag: picture %d to show isn't ready (%s): not shown\n", i,
|
||||
i >= 0 && i < SLOTS ? slot_names[slots[i].state] : "none");
|
||||
return;
|
||||
if (shown >= 0 && shown != i && slots[shown].state == SHOWN)
|
||||
}
|
||||
if (stopping) {
|
||||
if (diag_odd++ < 20)
|
||||
DIAG("diag: picture %d of the video before (stopping): dropped\n", i);
|
||||
/* the video before, on its way out: its last pictures aren't for
|
||||
* the screen (one came up in the next video's frame for a moment,
|
||||
* "a frame from another video", 2026-10-13) */
|
||||
slots[i].state = FREE;
|
||||
return;
|
||||
}
|
||||
if (shown >= 0 && shown != i && slots[shown].state == SHOWN) {
|
||||
slots[shown].state = FREE; /* never drawn: the screen was slower */
|
||||
if (diag_shown < DIAG_PICTURES)
|
||||
DIAG("diag: picture %d replaced before it was drawn\n", shown);
|
||||
}
|
||||
slots[i].state = SHOWN;
|
||||
shown = i;
|
||||
if (diag_shown < DIAG_PICTURES) {
|
||||
diag_shown++;
|
||||
DIAG("diag shown %d: buffer %d, setup %d, %.3f s after the open, frame %llu, luma %u\n",
|
||||
diag_shown, i, generation, plat_time() - opened_at,
|
||||
(unsigned long long)gfx.frame_number, slot_luma(slots[i], format));
|
||||
}
|
||||
if (first_picture_wait) {
|
||||
first_picture_wait = false;
|
||||
fprintf(stderr, "player: first picture %.2f s after the open\n", plat_time() - opened_at);
|
||||
fprintf(stderr, "player: [%.2f] first picture %.2f s after the open\n", plat_time(), plat_time() - opened_at);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -559,7 +661,6 @@ int amem_setup(void **opaque, char *format, unsigned *rate, unsigned *channels)
|
||||
}
|
||||
|
||||
/* True while a stop runs in the background: what VLC still sends is dropped. */
|
||||
std::atomic<bool> stopping;
|
||||
/* A video opened while the one before was still stopping: started after. */
|
||||
libvlc_media_t *queued_media;
|
||||
|
||||
@@ -766,10 +867,19 @@ void audio_main()
|
||||
|
||||
/* ---- libvlc ------------------------------------------------------------------- */
|
||||
|
||||
/* VLC's player, for a question from the interface: none while the video
|
||||
* before stops in the background. VLC's getters (time, length, tracks...)
|
||||
* wait for its stop then: 8.6 s on a live stream that was ending, and the
|
||||
* whole app froze (2026-10-13). */
|
||||
static libvlc_media_player_t *asking()
|
||||
{
|
||||
return stopping ? nullptr : mp;
|
||||
}
|
||||
|
||||
void read_info()
|
||||
{
|
||||
unsigned w = 0, h = 0;
|
||||
if (libvlc_video_get_size(mp, 0, &w, &h) != 0 || !w || !h)
|
||||
if (!asking() || libvlc_video_get_size(mp, 0, &w, &h) != 0 || !w || !h)
|
||||
return;
|
||||
VideoInfo v = {};
|
||||
v.width = w;
|
||||
@@ -981,6 +1091,8 @@ bool make_planes(const Format &f)
|
||||
planes.two_planes = f.two_planes;
|
||||
planes.buf_w = f.width;
|
||||
planes.buf_h = f.height;
|
||||
planes.vis_w = f.vis_w ? f.vis_w : f.width;
|
||||
planes.vis_h = f.vis_h ? f.vis_h : f.height;
|
||||
planes.has_picture = false;
|
||||
return true;
|
||||
}
|
||||
@@ -1008,7 +1120,10 @@ void vlc_log(void *data, int level, const libvlc_log_t *ctx, const char *fmt, va
|
||||
{
|
||||
(void)data;
|
||||
/* Normally errors only (a warning flood, "picture is too late", costs
|
||||
* nothing on screen now but fills the log); debug=1 in settings.txt: all. */
|
||||
* nothing on screen now but fills the log); debug=1 in settings.txt: all.
|
||||
* Pictures VLC drops as late and the clock's corrections are let through,
|
||||
* 40 per video at most: why a video stutters (planet_earth, 2026-10-18). */
|
||||
bool timing = false;
|
||||
if (level < LIBVLC_ERROR && !log_debug)
|
||||
return;
|
||||
static const char *const names[] = { "debug", "", "info", "warning", "error" };
|
||||
@@ -1017,6 +1132,10 @@ void vlc_log(void *data, int level, const libvlc_log_t *ctx, const char *fmt, va
|
||||
libvlc_log_get_context(ctx, &module, &file, &line);
|
||||
char msg[1024];
|
||||
vsnprintf(msg, sizeof(msg), fmt, args);
|
||||
if (timing) {
|
||||
fprintf(stderr, "vlc timing [%.2f]: %s\n", plat_time(), msg);
|
||||
return;
|
||||
}
|
||||
/* The console logger's own option, asked for by others; it isn't linked. */
|
||||
if (!strcmp(msg, "option quiet does not exist"))
|
||||
return;
|
||||
@@ -1069,14 +1188,48 @@ void load_decode_mode()
|
||||
}
|
||||
}
|
||||
|
||||
/* VLC's playback numbers, read on a thread of their own: read on the
|
||||
* interface's thread (the health check, every second), they once waited
|
||||
* 8.9 s for a live stream that was ending, and the whole app froze
|
||||
* (2026-10-13). The interface takes the latest copy. */
|
||||
std::thread stats_thread;
|
||||
std::atomic<bool> stats_run;
|
||||
std::mutex stats_lock;
|
||||
libvlc_media_stats_t stats_copy;
|
||||
bool stats_have;
|
||||
std::atomic<unsigned> stats_serial; /* a new video: numbers read before it are dropped */
|
||||
|
||||
void stats_loop()
|
||||
{
|
||||
while (stats_run) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(250));
|
||||
unsigned serial = stats_serial;
|
||||
libvlc_media_t *m = mp ? libvlc_media_player_get_media(mp) : nullptr;
|
||||
libvlc_media_stats_t s = {};
|
||||
bool ok = m && libvlc_media_get_stats(m, &s) != 0;
|
||||
if (m)
|
||||
libvlc_media_release(m);
|
||||
std::lock_guard<std::mutex> g(stats_lock);
|
||||
if (serial != stats_serial)
|
||||
continue; /* the video before's (negative counts, 2026-10-13) */
|
||||
stats_copy = s;
|
||||
stats_have = ok;
|
||||
}
|
||||
}
|
||||
|
||||
void stats_forget()
|
||||
{
|
||||
std::lock_guard<std::mutex> g(stats_lock);
|
||||
stats_serial++;
|
||||
stats_copy = {};
|
||||
stats_have = false;
|
||||
}
|
||||
|
||||
bool get_stats(libvlc_media_stats_t *s)
|
||||
{
|
||||
libvlc_media_t *m = mp ? libvlc_media_player_get_media(mp) : nullptr;
|
||||
if (!m)
|
||||
return false;
|
||||
bool ok = libvlc_media_get_stats(m, s) != 0;
|
||||
libvlc_media_release(m);
|
||||
return ok;
|
||||
std::lock_guard<std::mutex> g(stats_lock);
|
||||
*s = stats_copy;
|
||||
return stats_have;
|
||||
}
|
||||
|
||||
vlc_object_t *mp_object()
|
||||
@@ -1310,6 +1463,12 @@ extern "C" void vlc_ps5_copy_plane(uint8_t *dst, size_t dst_pitch, const uint8_t
|
||||
}
|
||||
|
||||
/* ps5/modules/ps5vdec.c, from VLC's decoder thread */
|
||||
/* ps5vdec and ps5http: their diagnostic lines with the detailed log only */
|
||||
extern "C" int vlc_ps5_log_details(void)
|
||||
{
|
||||
return log_debug;
|
||||
}
|
||||
|
||||
extern "C" void vlc_ps5_vdec_failed(void)
|
||||
{
|
||||
vdec_failed = true;
|
||||
@@ -1329,7 +1488,9 @@ bool player_init()
|
||||
}
|
||||
cpu_probe();
|
||||
load_decode_mode();
|
||||
log_debug = setting_int("debug", 0) != 0 || setting_int("vlc_debug", 0) != 0 || pref_int("debug", 0) != 0;
|
||||
/* settings.txt only: the Settings row is gone, and an old prefs.txt debug=1
|
||||
* would have left it on with no way to turn it off */
|
||||
log_debug = setting_int("debug", 0) != 0 || setting_int("vlc_debug", 0) != 0;
|
||||
/* The subtitle fonts from the app's folder, wherever it is now (it isn't
|
||||
* /app0 after the sandbox was lifted). */
|
||||
std::string font = "--freetype-font=" + std::string(plat_data_dir()) + "/fonts/subtitle.ttf";
|
||||
@@ -1399,6 +1560,8 @@ bool player_init()
|
||||
audio_thread = std::thread(audio_main);
|
||||
}
|
||||
fprintf(stderr, "player: libvlc %s ready\n", libvlc_get_version());
|
||||
stats_run = true;
|
||||
stats_thread = std::thread(stats_loop);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1423,8 +1586,15 @@ static void wait_for_stop()
|
||||
stopper.join();
|
||||
}
|
||||
|
||||
extern "C" int vlc_ps5_stream_cancelled(long serial)
|
||||
{
|
||||
return serial > 0 && serial <= cancelled_serial;
|
||||
}
|
||||
|
||||
static void stop_in_background()
|
||||
{
|
||||
if (started_serial > cancelled_serial)
|
||||
cancelled_serial = started_serial;
|
||||
/* already stopping: nothing new can have started since (an open waits
|
||||
* for the stop), and waiting for it here froze the app */
|
||||
if (stopping)
|
||||
@@ -1446,6 +1616,9 @@ static void stop_in_background()
|
||||
|
||||
void player_shutdown()
|
||||
{
|
||||
stats_run = false;
|
||||
if (stats_thread.joinable())
|
||||
stats_thread.join();
|
||||
wait_for_stop();
|
||||
if (mp) {
|
||||
libvlc_media_player_stop(mp);
|
||||
@@ -1482,9 +1655,13 @@ bool player_open(const std::string &path, int64_t start_ms, const std::vector<st
|
||||
: libvlc_media_new_path(vlc, path.c_str());
|
||||
if (!m)
|
||||
return false;
|
||||
for (const std::string &o : options)
|
||||
/* the same one again (hardware to software, a reconnect): its own
|
||||
* options too, or a channel that needs its user agent or referrer
|
||||
* failed on the reopen */
|
||||
const std::vector<std::string> &use = options.empty() && path == current_path ? source_options : options;
|
||||
for (const std::string &o : use)
|
||||
libvlc_media_add_option(m, o.c_str());
|
||||
return player_open_media(m, path, start_ms, options);
|
||||
return player_open_media(m, path, start_ms, std::vector<std::string>(use));
|
||||
}
|
||||
|
||||
std::string file_uri(const std::string &path);
|
||||
@@ -1673,6 +1850,10 @@ void side_subs_find(const std::string &url, int serial)
|
||||
bool link = url_scheme_is(url, "http") || url_scheme_is(url, "https");
|
||||
if (!listable && !link)
|
||||
return;
|
||||
static const char *const video_exts[] = { "mkv", "mp4", "m4v", "avi", "mov", "webm", "wmv", "flv", "ts",
|
||||
"m2ts", "mts", "mpg", "mpeg", "vob", "ogv", "3gp", "divx", "rmvb" };
|
||||
if (link && std::none_of(std::begin(video_exts), std::end(video_exts), [&](const char *e) { return ext == e; }))
|
||||
return; /* a channel's address (".../udp/239.94.1.24:5140"): no file beside it to guess */
|
||||
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 */
|
||||
@@ -1778,6 +1959,7 @@ static bool start_queued();
|
||||
bool player_open_media(libvlc_media_t *m, const std::string &path, int64_t start_ms,
|
||||
const std::vector<std::string> &options)
|
||||
{
|
||||
opened_from_ms = start_ms;
|
||||
/* A reopen of the same file (Auto's, Watch again) keeps its login. */
|
||||
if (!options.empty() || path != current_path)
|
||||
source_options = options;
|
||||
@@ -1796,7 +1978,20 @@ bool player_open_media(libvlc_media_t *m, const std::string &path, int64_t start
|
||||
}
|
||||
if (url) {
|
||||
side_subs_find(path, side.serial);
|
||||
/* Live TV over the network: 2 s of buffer, not VLC's 1. Channels
|
||||
* whose segments come in late made VLC grow its buffer as it played
|
||||
* ("PCR is called too late"), and the picture stuttered each time
|
||||
* (a BBC channel, 2026-10-13). Shares and files keep VLC's own. */
|
||||
if (url_scheme_is(path, "http") || url_scheme_is(path, "https") || url_scheme_is(path, "udp") ||
|
||||
url_scheme_is(path, "rtp"))
|
||||
libvlc_media_add_option(m, ":network-caching=2000");
|
||||
} else if (!local.empty()) {
|
||||
/* 1 s between decoding and showing, not VLC's 0.3 s for files: the
|
||||
* console's VP9 decoder gives a picture back a few pictures later,
|
||||
* and with YouTube's hidden frames at 24 fps that passed 0.3 s, so
|
||||
* VLC dropped 2 pictures a second as late (planet_earth, 2026-10-18).
|
||||
* A file reads in at once: it starts no later. */
|
||||
libvlc_media_add_option(m, ":file-caching=1000");
|
||||
std::vector<std::string> subs = local_side_subs(local);
|
||||
bool on = player_sub_language() != "off";
|
||||
std::string hint = sub_lang_hint();
|
||||
@@ -1852,7 +2047,7 @@ bool player_open_media(libvlc_media_t *m, const std::string &path, int64_t start
|
||||
libvlc_media_add_option(m, ":avcodec-skiploopfilter=4");
|
||||
libvlc_media_add_option(m, ":avcodec-fast");
|
||||
}
|
||||
bool software = path == software_path || setting_int("hw_decode", 1) == 0;
|
||||
bool software = path == software_path || !player_hw_decoding();
|
||||
if (software)
|
||||
libvlc_media_add_option(m, ":no-ps5vdec");
|
||||
live_paused = false;
|
||||
@@ -1861,21 +2056,31 @@ bool player_open_media(libvlc_media_t *m, const std::string &path, int64_t start
|
||||
info_tries = 0;
|
||||
current_path = path;
|
||||
health = Health{};
|
||||
stats_forget();
|
||||
/* Opening and the first second drop pictures anyway; a resume also seeks
|
||||
* and buffers first (run 19: one false "can't keep up" right after it). */
|
||||
health.next = plat_time() + (start_ms > 0 ? 5 : 3);
|
||||
debug_last = libvlc_media_stats_t{};
|
||||
fprintf(stderr, "player: open %s at %lld ms%s%s\n", path.c_str(), (long long)start_ms,
|
||||
fprintf(stderr, "player: [%.2f] open %s at %lld ms%s%s\n", plat_time(), path.c_str(), (long long)start_ms,
|
||||
fast_now ? " (fast decoding)" : "", software ? " (software decoding)" : "");
|
||||
/* The video before may still be stopping: a stream stuck on its network
|
||||
* kept VLC's stop waiting 9 s, and waiting for it here froze the whole
|
||||
* app (console run 12). This one then starts from player_tick once the
|
||||
* stop is done. */
|
||||
queued_serial = ++next_serial;
|
||||
{
|
||||
char opt[48];
|
||||
snprintf(opt, sizeof(opt), ":ps5http-serial=%ld", queued_serial);
|
||||
libvlc_media_add_option(m, opt);
|
||||
}
|
||||
if (!live_reconnecting)
|
||||
live_reconnects = 0;
|
||||
live_last_ms = live_last_len = 0;
|
||||
if (queued_media)
|
||||
libvlc_media_release(queued_media);
|
||||
queued_media = m;
|
||||
if (stopping) {
|
||||
fprintf(stderr, "player: the video before is still stopping: this one starts after\n");
|
||||
fprintf(stderr, "player: [%.2f] the video before is still stopping: this one starts after\n", plat_time());
|
||||
return true;
|
||||
}
|
||||
return start_queued();
|
||||
@@ -1889,7 +2094,26 @@ static bool start_queued()
|
||||
return false;
|
||||
queued_media = nullptr;
|
||||
wait_for_stop();
|
||||
started_serial = queued_serial;
|
||||
libvlc_media_player_set_media(mp, m);
|
||||
stats_forget(); /* until now the media was the video before */
|
||||
debug_last = libvlc_media_stats_t{};
|
||||
{
|
||||
/* the video before is gone now: a picture of it still waiting to go
|
||||
* up would show in this one's frame for a moment */
|
||||
std::lock_guard<std::mutex> g(pic_lock);
|
||||
if (shown >= 0 && slots[shown].state == SHOWN) {
|
||||
slots[shown].state = FREE;
|
||||
DIAG("diag: a picture of the video before was waiting: dropped\n");
|
||||
}
|
||||
shown = -1;
|
||||
char states[64] = "";
|
||||
for (const Slot &sl : slots)
|
||||
snprintf(states + strlen(states), sizeof(states) - strlen(states), "%s ",
|
||||
sl.buffer ? slot_names[sl.state] : "-");
|
||||
DIAG("diag: starting, setup %d, buffers [%s]\n", generation, states);
|
||||
diag_odd = 0;
|
||||
}
|
||||
if (last_media)
|
||||
libvlc_media_release(last_media);
|
||||
last_media = m; /* our reference: kept for a live pause */
|
||||
@@ -1915,6 +2139,21 @@ void player_set_decode_mode(DecodeMode mode)
|
||||
save_decode_mode();
|
||||
}
|
||||
|
||||
/* prefs.txt hw_decode (Settings); settings.txt hw_decode=0, the older way, still counts */
|
||||
bool player_hw_decoding()
|
||||
{
|
||||
return pref_int("hw_decode", setting_int("hw_decode", 1)) != 0;
|
||||
}
|
||||
|
||||
void player_set_hw_decoding(bool on)
|
||||
{
|
||||
pref_set("hw_decode", on ? 1 : 0);
|
||||
if (on)
|
||||
software_path.clear();
|
||||
/* from the next video: switching under a playing one held its picture a
|
||||
* second or more (the user's test) */
|
||||
}
|
||||
|
||||
bool player_fast_decoding()
|
||||
{
|
||||
return fast_now;
|
||||
@@ -1931,9 +2170,11 @@ bool playing_disc_now()
|
||||
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;
|
||||
return asking() && libvlc_media_player_get_title_count(mp) > 1;
|
||||
}
|
||||
|
||||
static bool live_dropped();
|
||||
|
||||
PlayerEvent player_tick()
|
||||
{
|
||||
double t = plat_time();
|
||||
@@ -1947,12 +2188,64 @@ PlayerEvent player_tick()
|
||||
add_ready_subtitle(p.path, p.ready);
|
||||
/* a video waiting for the one before to finish stopping */
|
||||
if (queued_media && !stopping) {
|
||||
fprintf(stderr, "player: the video before stopped: starting %s\n", current_path.c_str());
|
||||
fprintf(stderr, "player: [%.2f] the video before stopped: starting %s\n", plat_time(), current_path.c_str());
|
||||
start_queued();
|
||||
}
|
||||
if (live_dropped()) {
|
||||
live_reconnects++;
|
||||
fprintf(stderr, "player: live stream dropped at %lld of %lld ms: connecting again (%d of 5)\n",
|
||||
(long long)live_last_ms, (long long)live_last_len, live_reconnects);
|
||||
hold_picture = true;
|
||||
std::string path = current_path;
|
||||
stop_in_background();
|
||||
{
|
||||
std::lock_guard<std::mutex> g(audio_lock);
|
||||
audio_clear_locked();
|
||||
}
|
||||
live_reconnecting = true;
|
||||
player_open(path, 0);
|
||||
live_reconnecting = false;
|
||||
return PLAYER_EVENT_NONE;
|
||||
}
|
||||
if (stopping || queued_media)
|
||||
vdec_failed = false; /* the video before's decoder, on its way out */
|
||||
else if (mp && vdec_failed.exchange(false) && !current_path.empty()) {
|
||||
/* the console's decoder gave up on this video: FFmpeg, same place.
|
||||
* At once, not at the next health check (up to 3 s after the start:
|
||||
* the picture stood still that long, VP9 2026-10-13). */
|
||||
fprintf(stderr, "player: reopening %s without hardware decoding\n", current_path.c_str());
|
||||
hold_picture = true;
|
||||
restore_audio = libvlc_audio_get_track(mp);
|
||||
restore_spu = libvlc_video_get_spu(mp);
|
||||
software_path = current_path;
|
||||
int64_t at = libvlc_media_player_get_time(mp);
|
||||
/* given up in its first seconds: from where it started (VLC's clock
|
||||
* had run 2.7 s ahead of the picture, so it reopened further on and
|
||||
* looked frozen, VP9 2026-10-13) */
|
||||
if (plat_time() - opened_at < 6)
|
||||
at = opened_from_ms;
|
||||
std::string path = current_path;
|
||||
stop_in_background(); /* not here: VLC's stop froze the screen 1.5-3 s */
|
||||
{
|
||||
std::lock_guard<std::mutex> g(audio_lock);
|
||||
audio_clear_locked();
|
||||
}
|
||||
player_open(path, at > 0 ? at : 0);
|
||||
return PLAYER_EVENT_NONE;
|
||||
}
|
||||
if (!mp || t < health.next)
|
||||
return PLAYER_EVENT_NONE;
|
||||
health.next = t + 1;
|
||||
if (!stopping && !queued_media && state() == libvlc_Playing) {
|
||||
int64_t at = player_time();
|
||||
if (at > 0) {
|
||||
live_last_ms = at;
|
||||
live_last_len = player_length();
|
||||
/* a minute playing since the last reconnect: a fresh count */
|
||||
if (live_reconnects && at > 60000)
|
||||
live_reconnects = 0;
|
||||
}
|
||||
}
|
||||
if (state() == libvlc_Playing)
|
||||
side_subs_apply();
|
||||
if (sub_check_at > 0 && t >= sub_check_at && state() == libvlc_Playing) {
|
||||
@@ -1963,7 +2256,7 @@ PlayerEvent player_tick()
|
||||
int64_t at = libvlc_media_player_get_time(mp);
|
||||
std::string path = current_path;
|
||||
restore_spu = 1000; /* the last subtitle track: picked below once known */
|
||||
libvlc_media_player_stop(mp);
|
||||
stop_in_background(); /* not here: VLC's stop froze the screen 1.5-3 s */
|
||||
{
|
||||
std::lock_guard<std::mutex> g(audio_lock);
|
||||
audio_clear_locked();
|
||||
@@ -1994,22 +2287,6 @@ PlayerEvent player_tick()
|
||||
libvlc_video_set_spu_delay(mp, kept.spu_delay * 1000);
|
||||
restore_delays = false;
|
||||
}
|
||||
if (vdec_failed.exchange(false) && !current_path.empty()) {
|
||||
/* the console's decoder gave up on this video: FFmpeg, same place */
|
||||
fprintf(stderr, "player: reopening %s without hardware decoding\n", current_path.c_str());
|
||||
restore_audio = libvlc_audio_get_track(mp);
|
||||
restore_spu = libvlc_video_get_spu(mp);
|
||||
software_path = current_path;
|
||||
int64_t at = libvlc_media_player_get_time(mp);
|
||||
std::string path = current_path;
|
||||
libvlc_media_player_stop(mp);
|
||||
{
|
||||
std::lock_guard<std::mutex> g(audio_lock);
|
||||
audio_clear_locked();
|
||||
}
|
||||
player_open(path, at > 0 ? at : 0);
|
||||
return PLAYER_EVENT_NONE;
|
||||
}
|
||||
libvlc_media_stats_t s;
|
||||
if (!get_stats(&s))
|
||||
return PLAYER_EVENT_NONE;
|
||||
@@ -2032,6 +2309,14 @@ PlayerEvent player_tick()
|
||||
* reopen would throw the menus away. */
|
||||
if (playing_disc_now())
|
||||
playing = false;
|
||||
/* Nor on a network stream: there too few pictures means the server
|
||||
* isn't sending fast enough (BBC America's sent a 4 s piece in 3-12 s,
|
||||
* 2026-10-13), not slow decoding, and the reopen in fast decoding threw
|
||||
* away what had come in: the channel froze from the start. */
|
||||
if (url_scheme_is(current_path, "http") || url_scheme_is(current_path, "https") ||
|
||||
url_scheme_is(current_path, "udp") || url_scheme_is(current_path, "rtp") ||
|
||||
url_scheme_is(current_path, "rtsp") || url_scheme_is(current_path, "mms"))
|
||||
playing = false;
|
||||
bool bad = playing && decoded > 0 &&
|
||||
(lost > shown || shown < fps * 0.5f);
|
||||
health.bad_seconds = bad ? health.bad_seconds + 1 : 0;
|
||||
@@ -2044,12 +2329,13 @@ PlayerEvent player_tick()
|
||||
/* fast decoding is FFmpeg's: nothing to gain on the console's decoder */
|
||||
if (!fast_now && decode_mode == DECODE_AUTO && vdec_active == 0) {
|
||||
fprintf(stderr, "player: switching %s to fast decoding\n", current_path.c_str());
|
||||
hold_picture = true;
|
||||
restore_audio = libvlc_audio_get_track(mp);
|
||||
restore_spu = libvlc_video_get_spu(mp);
|
||||
fast_path = current_path;
|
||||
int64_t at = libvlc_media_player_get_time(mp);
|
||||
std::string path = current_path;
|
||||
libvlc_media_player_stop(mp);
|
||||
stop_in_background(); /* not here: VLC's stop froze the screen 1.5-3 s */
|
||||
{
|
||||
std::lock_guard<std::mutex> g(audio_lock);
|
||||
audio_clear_locked();
|
||||
@@ -2083,8 +2369,30 @@ static libvlc_state_t state()
|
||||
return mp ? libvlc_media_player_get_state(mp) : libvlc_NothingSpecial;
|
||||
}
|
||||
|
||||
static bool is_stream(const std::string &p)
|
||||
{
|
||||
return url_scheme_is(p, "http") || url_scheme_is(p, "https") || url_scheme_is(p, "udp") ||
|
||||
url_scheme_is(p, "rtp") || url_scheme_is(p, "rtsp") || url_scheme_is(p, "mms");
|
||||
}
|
||||
|
||||
/* A stream that played and then ended well before its end (a live channel
|
||||
* has none, or a window it never reaches): dropped, not finished. */
|
||||
static bool live_dropped()
|
||||
{
|
||||
if (queued_media || stopping || current_path.empty() || live_reconnects >= 5 || !is_stream(current_path))
|
||||
return false;
|
||||
libvlc_state_t s = state();
|
||||
if (s != libvlc_Ended && s != libvlc_Error)
|
||||
return false;
|
||||
if (live_last_ms <= 0)
|
||||
return false; /* never played: offline, not dropped */
|
||||
return live_last_len <= 0 || live_last_ms < live_last_len - 5000;
|
||||
}
|
||||
|
||||
bool player_active()
|
||||
{
|
||||
if (live_dropped())
|
||||
return true; /* about to reconnect */
|
||||
libvlc_state_t s = state();
|
||||
return live_paused || queued_media || s == libvlc_Opening || s == libvlc_Buffering ||
|
||||
s == libvlc_Playing || s == libvlc_Paused;
|
||||
@@ -2092,8 +2400,8 @@ bool player_active()
|
||||
|
||||
bool player_ended()
|
||||
{
|
||||
if (queued_media)
|
||||
return false; /* the next one is about to start */
|
||||
if (queued_media || live_dropped())
|
||||
return false; /* the next one is about to start, or a reconnect */
|
||||
libvlc_state_t s = state();
|
||||
return s == libvlc_Ended || s == libvlc_Error;
|
||||
}
|
||||
@@ -2131,7 +2439,7 @@ bool player_in_menu()
|
||||
return menu;
|
||||
next = now + 0.25;
|
||||
menu = false;
|
||||
if (!mp)
|
||||
if (!asking())
|
||||
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
|
||||
@@ -2174,7 +2482,7 @@ bool player_in_menu()
|
||||
* Blu-ray's pop-up menu, over the film. */
|
||||
void player_disc_menu(bool popup)
|
||||
{
|
||||
if (!mp)
|
||||
if (!asking())
|
||||
return;
|
||||
if (input_thread_t *input = libvlc_get_input_thread(mp)) {
|
||||
vlc_object_t *in = (vlc_object_t *)input;
|
||||
@@ -2189,7 +2497,7 @@ void player_disc_menu(bool popup)
|
||||
bool player_disc_has_popup()
|
||||
{
|
||||
bool popup = false;
|
||||
if (mp)
|
||||
if (asking())
|
||||
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");
|
||||
@@ -2258,12 +2566,12 @@ void player_toggle_pause()
|
||||
|
||||
int64_t player_time()
|
||||
{
|
||||
return mp ? libvlc_media_player_get_time(mp) : 0;
|
||||
return asking() ? libvlc_media_player_get_time(mp) : 0;
|
||||
}
|
||||
|
||||
int64_t player_length()
|
||||
{
|
||||
int64_t l = mp ? libvlc_media_player_get_length(mp) : 0;
|
||||
int64_t l = asking() ? libvlc_media_player_get_length(mp) : 0;
|
||||
return l > 0 ? l : 0;
|
||||
}
|
||||
|
||||
@@ -2297,7 +2605,7 @@ void player_set_volume(int volume)
|
||||
|
||||
float player_rate()
|
||||
{
|
||||
return mp ? libvlc_media_player_get_rate(mp) : 1.0f;
|
||||
return asking() ? libvlc_media_player_get_rate(mp) : 1.0f;
|
||||
}
|
||||
|
||||
void player_set_rate(float rate)
|
||||
@@ -2308,12 +2616,12 @@ void player_set_rate(float rate)
|
||||
|
||||
std::vector<Track> player_audio_tracks()
|
||||
{
|
||||
return tracks_from(mp ? libvlc_audio_get_track_description(mp) : nullptr);
|
||||
return tracks_from(asking() ? libvlc_audio_get_track_description(mp) : nullptr);
|
||||
}
|
||||
|
||||
int player_audio_track()
|
||||
{
|
||||
return mp ? libvlc_audio_get_track(mp) : -1;
|
||||
return asking() ? libvlc_audio_get_track(mp) : -1;
|
||||
}
|
||||
|
||||
void player_set_audio_track(int id)
|
||||
@@ -2325,12 +2633,12 @@ void player_set_audio_track(int id)
|
||||
|
||||
std::vector<Track> player_subtitle_tracks()
|
||||
{
|
||||
return tracks_from(mp ? libvlc_video_get_spu_description(mp) : nullptr);
|
||||
return tracks_from(asking() ? libvlc_video_get_spu_description(mp) : nullptr);
|
||||
}
|
||||
|
||||
int player_subtitle_track()
|
||||
{
|
||||
return mp ? libvlc_video_get_spu(mp) : -1;
|
||||
return asking() ? libvlc_video_get_spu(mp) : -1;
|
||||
}
|
||||
|
||||
void player_set_subtitle_track(int id)
|
||||
@@ -2472,15 +2780,35 @@ void video_upload(VkCommandBuffer cmd)
|
||||
slot = shown;
|
||||
}
|
||||
pic_freed.notify_all();
|
||||
/* reopened: the last picture stays until there's a new one to show */
|
||||
if (gen != planes.generation && gen > 0 && slot < 0 && hold_picture && planes.has_picture)
|
||||
return;
|
||||
if (gen != planes.generation && gen > 0) {
|
||||
DIAG("diag: setup %d on screen (was %d): %ux%u%s, picture up %d, hold %d, waiting %d\n", gen,
|
||||
planes.generation, f.width, f.height, f.two_planes ? " two planes" : "",
|
||||
(int)planes.has_picture, (int)hold_picture.load(), slot);
|
||||
hold_picture = false;
|
||||
/* The same shape again (the file restarted, Watch again, a loop):
|
||||
* the textures stay, and so does the last picture until the next
|
||||
* one comes; remade, the video blinked black. */
|
||||
bool same = planes.image[0] && f.width == planes.buf_w && f.height == planes.buf_h &&
|
||||
f.ten_bit == planes.ten_bit && f.full_range == planes.full_range &&
|
||||
f.two_planes == planes.two_planes;
|
||||
if (!same && !make_planes(f))
|
||||
DIAG("diag: textures %s\n", same ? "kept (same shape)" : "made new");
|
||||
static double planes_failed_at;
|
||||
if (!same && plat_time() - planes_failed_at < 0.5)
|
||||
return;
|
||||
if (!same && !make_planes(f)) {
|
||||
planes_failed_at = plat_time();
|
||||
return;
|
||||
}
|
||||
/* only when the shape changes: 3840x2160 to 3840x2176 with the
|
||||
* same picture in it (hardware to software) skipped a frame for
|
||||
* nothing, a black flash (2026-10-18) */
|
||||
float was = planes.prev_aspect, now_aspect = f.vis_h ? (float)f.vis_w / f.vis_h : 0;
|
||||
if (!same && (was <= 0 || now_aspect <= 0 || fabsf(was / now_aspect - 1) > 0.01f))
|
||||
planes.new_shape_frame = gfx.frame_number;
|
||||
planes.prev_aspect = now_aspect;
|
||||
planes.generation = gen;
|
||||
planes.colour = f.colour;
|
||||
}
|
||||
@@ -2492,6 +2820,12 @@ void video_upload(VkCommandBuffer cmd)
|
||||
if (slots[slot].state != SHOWN)
|
||||
return;
|
||||
buffer = slots[slot].buffer;
|
||||
if (diag_up < DIAG_PICTURES) {
|
||||
diag_up++;
|
||||
DIAG("diag upload %d: buffer %d, setup %d, frame %llu, luma %u, picture up before %d\n",
|
||||
diag_up, slot, gen, (unsigned long long)gfx.frame_number + 1, slot_luma(slots[slot], f),
|
||||
(int)planes.has_picture);
|
||||
}
|
||||
slots[slot].state = GPU;
|
||||
slots[slot].gpu_frame = gfx.frame_number + 1;
|
||||
if (shown == slot)
|
||||
@@ -2521,30 +2855,81 @@ void video_upload(VkCommandBuffer cmd)
|
||||
planes.has_picture = true;
|
||||
}
|
||||
|
||||
double player_open_age()
|
||||
{
|
||||
return plat_time() - opened_at;
|
||||
}
|
||||
|
||||
bool video_has_picture()
|
||||
{
|
||||
return planes.has_picture;
|
||||
}
|
||||
|
||||
const std::string &player_current_path()
|
||||
{
|
||||
return current_path;
|
||||
}
|
||||
|
||||
bool player_match_rate()
|
||||
{
|
||||
return pref_int("match_rate", 0) != 0;
|
||||
}
|
||||
|
||||
void player_set_match_rate(bool on)
|
||||
{
|
||||
pref_set("match_rate", on ? 1 : 0);
|
||||
}
|
||||
|
||||
bool video_wants_hdr_output()
|
||||
{
|
||||
/* settings.txt hdr_output: 0 never, 1 when the console's output is HDR
|
||||
* (the default), 2 always (a TV the console doesn't report as HDR) */
|
||||
* (the default), 2 always (a TV the console doesn't report as HDR). It
|
||||
* follows the console: trying HDR on an SDR output was taken out
|
||||
* (the user: the PS5 switches the TV itself when it's set to). */
|
||||
static const int mode = setting_int("hdr_output", 1);
|
||||
if (mode == 0 || !planes.has_picture || planes.colour.transfer == TRC_SDR || !player_active())
|
||||
return false;
|
||||
return mode == 2 || gfx.display_range == 2;
|
||||
}
|
||||
|
||||
float video_picture_aspect()
|
||||
{
|
||||
return format.vis_w && format.vis_h ? (float)format.vis_w / format.vis_h : 0;
|
||||
}
|
||||
|
||||
void video_forget_picture()
|
||||
{
|
||||
DIAG("diag: [%.2f] picture taken off the screen (was up %d)\n", plat_time(), (int)planes.has_picture);
|
||||
planes.has_picture = false;
|
||||
hold_picture = false;
|
||||
/* and a picture of it still waiting to go up */
|
||||
std::lock_guard<std::mutex> g(pic_lock);
|
||||
if (shown >= 0 && slots[shown].state == SHOWN)
|
||||
slots[shown].state = FREE;
|
||||
shown = -1;
|
||||
}
|
||||
|
||||
void video_draw(VkCommandBuffer cmd, float x, float y, float w, float h)
|
||||
{
|
||||
if (!planes.has_picture)
|
||||
return;
|
||||
/* A new shape's first frame: the rectangle was worked out before the
|
||||
* picture came, from the video before's shape. With the console's
|
||||
* decoder the size and the first picture come in the same frame, and a
|
||||
* portrait video flashed stretched over the whole screen after a 16:9
|
||||
* one ("a frame from another video", caught by the log 2026-10-18).
|
||||
* Nothing this one frame; the next is placed right. */
|
||||
if (planes.new_shape_frame == gfx.frame_number) {
|
||||
DIAG("diag draw: frame %llu skipped, the rectangle is the shape before's\n",
|
||||
(unsigned long long)gfx.frame_number);
|
||||
return;
|
||||
}
|
||||
if (diag_draws < 6) {
|
||||
diag_draws++;
|
||||
DIAG("diag draw: frame %llu, textures of setup %d (now %d), %ux%u at %.0f,%.0f %.0fx%.0f\n",
|
||||
(unsigned long long)gfx.frame_number, planes.generation, generation, planes.buf_w,
|
||||
planes.buf_h, x, y, w, h);
|
||||
}
|
||||
const VideoInfo &v = player_video_info();
|
||||
Push p = {};
|
||||
/* The matrix VLC names; if it doesn't, BT.709 for HD and BT.601 for SD. */
|
||||
@@ -2576,11 +2961,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;
|
||||
}
|
||||
/* 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;
|
||||
/* Only the picture, not the decoder's padding rows (1280x720 of a
|
||||
* 1280x736 buffer): vmem passes the buffer as it is (patches/0018)
|
||||
* instead of VLC scaling every picture to fill it on the CPU. */
|
||||
p.uv_scale[0] = planes.buf_w ? (float)planes.vis_w / planes.buf_w : 1.0f;
|
||||
p.uv_scale[1] = planes.buf_h ? (float)planes.vis_h / planes.buf_h : 1.0f;
|
||||
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;
|
||||
@@ -2606,7 +2991,7 @@ void player_view(float yaw, float pitch, float fov)
|
||||
|
||||
int64_t player_audio_delay()
|
||||
{
|
||||
return mp ? libvlc_audio_get_delay(mp) / 1000 : 0;
|
||||
return asking() ? libvlc_audio_get_delay(mp) / 1000 : 0;
|
||||
}
|
||||
|
||||
void player_set_audio_delay(int64_t ms)
|
||||
@@ -2618,7 +3003,7 @@ void player_set_audio_delay(int64_t ms)
|
||||
|
||||
int64_t player_subtitle_delay()
|
||||
{
|
||||
return mp ? libvlc_video_get_spu_delay(mp) / 1000 : 0;
|
||||
return asking() ? libvlc_video_get_spu_delay(mp) / 1000 : 0;
|
||||
}
|
||||
|
||||
void player_set_subtitle_delay(int64_t ms)
|
||||
@@ -2642,7 +3027,7 @@ static bool title_is_menu(int title)
|
||||
|
||||
std::vector<Chapter> player_chapters()
|
||||
{
|
||||
if (!mp)
|
||||
if (!asking())
|
||||
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. */
|
||||
@@ -2676,7 +3061,7 @@ std::vector<Chapter> player_chapters()
|
||||
|
||||
int player_chapter()
|
||||
{
|
||||
return mp ? libvlc_media_player_get_chapter(mp) : -1;
|
||||
return asking() ? libvlc_media_player_get_chapter(mp) : -1;
|
||||
}
|
||||
|
||||
void player_set_chapter(int index)
|
||||
@@ -2702,8 +3087,8 @@ bool player_screenshot(const std::string &path)
|
||||
shot.data.assign(slots[last_slot].mapped, slots[last_slot].mapped + format.size);
|
||||
}
|
||||
/* the whole buffer: the picture fills it (see video_draw) */
|
||||
shot.w = shot.f.width;
|
||||
shot.h = shot.f.height;
|
||||
shot.w = shot.f.vis_w ? shot.f.vis_w : shot.f.width;
|
||||
shot.h = shot.f.vis_h ? shot.f.vis_h : shot.f.height;
|
||||
shot.path = path;
|
||||
std::thread(shot_main, std::move(shot)).detach();
|
||||
return true;
|
||||
|
||||
@@ -90,10 +90,18 @@ void player_spectrum(float *bands, int n);
|
||||
/* Render thread, every frame: */
|
||||
void video_upload(VkCommandBuffer cmd); /* before the render pass */
|
||||
bool video_has_picture();
|
||||
/* seconds since the video was opened */
|
||||
double player_open_age();
|
||||
/* An HDR video is showing and HDR10 output is wanted for it (settings.txt
|
||||
* hdr_output, the console's output). */
|
||||
bool video_wants_hdr_output();
|
||||
/* Match frame rate: films at 23.976 / 24 / 25 / 50 fps switch the TV to that
|
||||
* refresh while they play (saved; Off by default: some TVs blank a moment). */
|
||||
bool player_match_rate();
|
||||
const std::string &player_current_path(); /* what plays (or was last opened) */
|
||||
void player_set_match_rate(bool on);
|
||||
void video_draw(VkCommandBuffer cmd, float x, float y, float w, float h); /* in the pass */
|
||||
float video_picture_aspect(); /* the pictures VLC sends now: width / height (0: none yet) */
|
||||
void video_forget_picture(); /* back to the library */
|
||||
/* One line about the player in the log (state, time, picture slots, sound queue,
|
||||
* pictures decoded/shown/lost since the last line). */
|
||||
@@ -178,6 +186,10 @@ void player_set_debug_log(bool on);
|
||||
DecodeMode player_decode_mode();
|
||||
void player_set_decode_mode(DecodeMode mode); /* saved; applies from the next file */
|
||||
bool player_fast_decoding(); /* the current file decodes fast */
|
||||
/* Hardware decoding (the console's video decoder) or FFmpeg on the CPU:
|
||||
* saved; applies from the next video. */
|
||||
bool player_hw_decoding();
|
||||
void player_set_hw_decoding(bool on);
|
||||
/* Main thread, every frame while playing: watches for lost pictures. */
|
||||
enum PlayerEvent { PLAYER_EVENT_NONE, PLAYER_EVENT_WENT_FAST, PLAYER_EVENT_TOO_SLOW };
|
||||
PlayerEvent player_tick();
|
||||
@@ -171,9 +171,11 @@ 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" */
|
||||
/* libbluray without a JVM (none runs on the console): not "install Java".
|
||||
* Said plainly: "it plays without them" promised a film that often
|
||||
* didn't come (the user, 2026-10-18). */
|
||||
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";
|
||||
return "Java Blu-ray discs (BD-J) aren't supported on the PS5";
|
||||
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;
|
||||
@@ -544,6 +546,15 @@ void ci_add(float cx, float cy, float s)
|
||||
rect(cx + s * 0.278f, cy + s * 0.17f, s * 0.045f, s * 0.18f, IM_COL32(255, 255, 255, 255));
|
||||
}
|
||||
|
||||
void icon_link(float cx, float cy, float s, ImU32 col);
|
||||
|
||||
/* Open a network link: a blue disc with a chain link */
|
||||
void ci_link(float cx, float cy, float s)
|
||||
{
|
||||
circle(cx, cy, s * 0.38f, IM_COL32(52, 120, 214, 255));
|
||||
icon_link(cx, cy, s * 0.46f, IM_COL32(255, 255, 255, 255));
|
||||
}
|
||||
|
||||
/* ---- icons, drawn so they stay sharp at any size ----------------------------------- */
|
||||
|
||||
void icon_play(float cx, float cy, float s, ImU32 col)
|
||||
@@ -1020,7 +1031,7 @@ void rebuild_lists()
|
||||
|
||||
/* Network places in Browse: a server (saved SMB share or DLNA server), a
|
||||
* folder or file on it, and "Add a network share". */
|
||||
enum { NET_NONE, NET_SERVER, NET_DIR, NET_FILE, NET_ADD, NET_ONLINE };
|
||||
enum { NET_NONE, NET_SERVER, NET_DIR, NET_FILE, NET_ADD, NET_ONLINE, NET_LINK };
|
||||
|
||||
struct BrowseEntry {
|
||||
std::string name, path;
|
||||
@@ -1122,6 +1133,7 @@ void rebuild_browse()
|
||||
for (const NetServer &sv : net_servers())
|
||||
browse_entries.push_back({ sv.name, sv.url, true, -1, false, NET_SERVER });
|
||||
browse_entries.push_back({ "Add a network share", "", false, -1, false, NET_ADD });
|
||||
browse_entries.push_back({ "Open a network link", "", false, -1, false, NET_LINK });
|
||||
browse_entries.push_back({ "Free TV channels", "tv:", true, -1, false, NET_ONLINE });
|
||||
browse_entries.push_back({ "Internet radio", "radio:", true, -1, false, NET_ONLINE });
|
||||
return;
|
||||
@@ -1253,8 +1265,11 @@ void save_position(bool finished)
|
||||
{
|
||||
if (playing_path.empty() || is_link(playing_path))
|
||||
return;
|
||||
int64_t t = finished ? 0 : player_time();
|
||||
library_set_resume(playing_path, t, player_length());
|
||||
int64_t t = finished ? 0 : player_time(), len = player_length();
|
||||
/* nothing known (the player busy stopping): the saved spot stays */
|
||||
if (!finished && len <= 0)
|
||||
return;
|
||||
library_set_resume(playing_path, t, len);
|
||||
}
|
||||
|
||||
void leave_player(bool finished)
|
||||
@@ -2446,6 +2461,7 @@ std::vector<std::pair<uint32_t, const char *>> playlist_hints()
|
||||
void start_stream(const std::string &url, const std::vector<std::string> &options = {}, bool remember = true,
|
||||
const std::string &name = "", libvlc_media_t *media = nullptr);
|
||||
void open_add_share();
|
||||
void open_link();
|
||||
|
||||
void spinner(float cx, float cy, float r);
|
||||
|
||||
@@ -2603,7 +2619,7 @@ void classic_label(const BrowseEntry &e, std::string *name, std::string *type, i
|
||||
if (e.net == NET_SERVER) {
|
||||
*type = net_is_saved_server(e.path) ? "Network share (SMB)" : "Media server (DLNA)";
|
||||
*group = 1;
|
||||
} else if (e.net == NET_ADD) {
|
||||
} else if (e.net == NET_ADD || e.net == NET_LINK) {
|
||||
*type = "";
|
||||
*group = 1;
|
||||
} else if (e.net == NET_ONLINE) {
|
||||
@@ -2669,6 +2685,8 @@ void classic_icon(const BrowseEntry &e, float cx, float cy, float s)
|
||||
ci_server(cx, cy, s);
|
||||
else if (e.net == NET_ADD)
|
||||
ci_add(cx, cy, s);
|
||||
else if (e.net == NET_LINK)
|
||||
ci_link(cx, cy, 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)
|
||||
@@ -2908,6 +2926,8 @@ void mini_player()
|
||||
text(f_reg, 17, kx + 46, y + 17, C_DIM, "Open");
|
||||
}
|
||||
|
||||
void open_search(const std::string &text = "");
|
||||
|
||||
void tab_browse()
|
||||
{
|
||||
/* The network listing or the servers found changed: rebuild. */
|
||||
@@ -2976,6 +2996,8 @@ void tab_browse()
|
||||
BrowseEntry e = browse_entries[browse_focus];
|
||||
if (e.net == NET_ADD) {
|
||||
open_add_share();
|
||||
} else if (e.net == NET_LINK) {
|
||||
open_link();
|
||||
} else if (e.net == NET_SERVER || e.net == NET_DIR || e.net == NET_ONLINE) {
|
||||
net_enter(e.path, e.name);
|
||||
} else {
|
||||
@@ -3084,6 +3106,8 @@ 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_LINK)
|
||||
icon_link(146, y + 33, 30, mix(C_DIM, C_ORANGE, f));
|
||||
else if (e.net == NET_FILE) {
|
||||
int k = name_kind(e.name);
|
||||
if (k == 1)
|
||||
@@ -3322,8 +3346,16 @@ void kb_backspace()
|
||||
|
||||
/* Draws the keyboard at (x, y), w wide; handles the buttons when it has the
|
||||
* focus. Returns true on Done. *leave_up: ↑ was pressed on the top row. */
|
||||
#ifdef VLCPS5_HOST
|
||||
extern "C" const char *plat_take_typed(void); /* the test script's "type" steps */
|
||||
#endif
|
||||
|
||||
bool kb_frame(float x, float y, float w, bool focused, float a, bool *leave_up)
|
||||
{
|
||||
#ifdef VLCPS5_HOST
|
||||
if (const char *typed = plat_take_typed())
|
||||
kb.text += typed;
|
||||
#endif
|
||||
const float gap = 10, kh = 62;
|
||||
float kw = (w - 9 * gap) / 10;
|
||||
std::vector<int> bottom = kb_bottom();
|
||||
@@ -3474,15 +3506,24 @@ void text_box(float x, float y, float w, const char *placeholder, float a, bool
|
||||
|
||||
/* ---- Search and Open a network link -------------------------------------------- */
|
||||
|
||||
enum FoundKind { F_ITEM, F_PLAYLIST, F_BROWSE, F_SERVER, F_LINK, F_TV, F_STATION, F_BUSY };
|
||||
struct Found {
|
||||
int item; /* library index, or -1 */
|
||||
int playlist; /* playlist index, or -1 */
|
||||
std::string link;
|
||||
int kind;
|
||||
int index = -1; /* library item, playlist or Browse entry */
|
||||
std::string link; /* a server's or a recent link's address */
|
||||
};
|
||||
std::vector<Found> found;
|
||||
int found_focus;
|
||||
bool in_list; /* focus on the list (above the keyboard) */
|
||||
std::string found_for = "\x01";
|
||||
int found_serial = -1; /* the Browse network listing searched */
|
||||
int found_online = -1; /* the online results searched (net_search_serial) */
|
||||
|
||||
/* Searching from inside a network list (a channel list, a share): its entries too. */
|
||||
bool search_in_browse()
|
||||
{
|
||||
return screen == LIBRARY && tab == BROWSE && !net_path.empty() && !sub_pick;
|
||||
}
|
||||
|
||||
std::string lower_ascii(std::string s)
|
||||
{
|
||||
@@ -3491,26 +3532,78 @@ std::string lower_ascii(std::string s)
|
||||
return s;
|
||||
}
|
||||
|
||||
std::vector<std::string> recent_links();
|
||||
std::string link_name(const std::string &url);
|
||||
|
||||
/* Everything the app knows by name: the list open in Browse (channels, a
|
||||
* share's folder), playlists, videos and songs, saved shares and servers,
|
||||
* recent links, and the online lists (every free TV channel, radio stations
|
||||
* by name). */
|
||||
void update_search()
|
||||
{
|
||||
if (found_for == kb.text)
|
||||
int serial = search_in_browse() ? net_list_serial() : -1;
|
||||
net_search_typed(kb.text);
|
||||
int online = net_search_serial() * 2 + (net_search_busy() ? 1 : 0);
|
||||
if (found_for == kb.text && found_serial == serial && found_online == online)
|
||||
return;
|
||||
bool typed = found_for != kb.text;
|
||||
found_for = kb.text;
|
||||
found_serial = serial;
|
||||
found_online = online;
|
||||
found.clear();
|
||||
if (typed)
|
||||
found_focus = 0;
|
||||
std::string q = lower_ascii(kb.text);
|
||||
while (!q.empty() && q.back() == ' ')
|
||||
q.pop_back();
|
||||
while (!q.empty() && q[0] == ' ')
|
||||
q.erase(0, 1);
|
||||
if (q.empty())
|
||||
return;
|
||||
auto has = [&](const std::string &name) { return lower_ascii(name).find(q) != std::string::npos; };
|
||||
if (serial >= 0) {
|
||||
/* The list open in Browse first: that's what is being looked through. */
|
||||
int got = 0;
|
||||
for (int i = 0; i < (int)browse_entries.size() && got < 200; i++)
|
||||
if (browse_entries[i].net != NET_NONE && browse_entries[i].net != NET_ADD && browse_entries[i].net != NET_LINK && has(browse_entries[i].name)) {
|
||||
found.push_back({ F_BROWSE, i });
|
||||
got++;
|
||||
}
|
||||
}
|
||||
auto &pls = library_playlists();
|
||||
for (int i = 0; i < (int)pls.size(); i++)
|
||||
if (lower_ascii(pls[i].name).find(q) != std::string::npos)
|
||||
found.push_back({ -1, i, "" });
|
||||
for (int i = 0; i < (int)L().size() && found.size() < 60; i++)
|
||||
if (lower_ascii(L()[i].name).find(q) != std::string::npos ||
|
||||
lower_ascii(L()[i].folder.substr(L()[i].folder.rfind('/') + 1)).find(q) != std::string::npos)
|
||||
found.push_back({ i, -1, "" });
|
||||
found_focus = 0;
|
||||
if (has(pls[i].name))
|
||||
found.push_back({ F_PLAYLIST, i });
|
||||
int items = 0;
|
||||
for (int i = 0; i < (int)L().size() && items < 100; i++)
|
||||
if (has(L()[i].name) || has(L()[i].folder.substr(L()[i].folder.rfind('/') + 1))) {
|
||||
found.push_back({ F_ITEM, i });
|
||||
items++;
|
||||
}
|
||||
for (const NetServer &sv : net_servers())
|
||||
if (has(sv.name) || has(sv.url))
|
||||
found.push_back({ F_SERVER, -1, sv.url });
|
||||
for (const std::string &l : recent_links())
|
||||
if (has(link_name(l)) || has(l))
|
||||
found.push_back({ F_LINK, -1, l });
|
||||
/* Online, as it comes: free TV channels and radio stations, played
|
||||
* straight from here */
|
||||
if (q.size() >= 2) {
|
||||
const std::vector<NetEntry> &tv = net_search_tv();
|
||||
int got = 0;
|
||||
for (int i = 0; i < (int)tv.size() && got < 60; i++)
|
||||
if (has(tv[i].name)) {
|
||||
found.push_back({ F_TV, i });
|
||||
got++;
|
||||
}
|
||||
const std::vector<NetEntry> &radio = net_search_radio();
|
||||
for (int i = 0; i < (int)radio.size() && i < 40; i++)
|
||||
found.push_back({ F_STATION, i });
|
||||
if (net_search_busy())
|
||||
found.push_back({ F_BUSY });
|
||||
}
|
||||
if (found_focus >= (int)found.size())
|
||||
found_focus = found.empty() ? 0 : (int)found.size() - 1;
|
||||
}
|
||||
|
||||
/* Recent links, newest first: cache/links.txt. */
|
||||
@@ -3626,9 +3719,9 @@ void start_stream(const std::string &url, const std::vector<std::string> &option
|
||||
was_active = false;
|
||||
}
|
||||
|
||||
void open_search()
|
||||
void open_search(const std::string &text)
|
||||
{
|
||||
kb_open("", false);
|
||||
kb_open(text, false);
|
||||
found_for = "\x01";
|
||||
in_list = false;
|
||||
modal = SEARCH;
|
||||
@@ -4424,12 +4517,46 @@ void modal_text_screen()
|
||||
std::string title, sub;
|
||||
if (search) {
|
||||
const Found &r = found[i];
|
||||
if (r.playlist >= 0) {
|
||||
if (r.kind == F_TV) {
|
||||
icon_tv(x + 22, ry + 26, 28, alpha(C_DIM, a));
|
||||
title = net_search_tv()[r.index].name;
|
||||
sub = tr("Free TV channels");
|
||||
} else if (r.kind == F_STATION) {
|
||||
icon_radio(x + 22, ry + 26, 28, alpha(C_DIM, a));
|
||||
title = net_search_radio()[r.index].name;
|
||||
sub = tr("Internet radio");
|
||||
} else if (r.kind == F_BUSY) {
|
||||
spinner(x + 22, ry + 26, 12);
|
||||
title = tr("Looking for channels and stations\xE2\x80\xA6");
|
||||
} else if (r.kind == F_SERVER) {
|
||||
icon_server(x + 22, ry + 26, 28, alpha(C_DIM, a));
|
||||
title = r.link;
|
||||
for (const NetServer &sv : net_servers())
|
||||
if (sv.url == r.link)
|
||||
title = sv.name;
|
||||
sub = tr("Network");
|
||||
} else if (r.kind == F_LINK) {
|
||||
icon_link(x + 22, ry + 26, 28, alpha(C_DIM, a));
|
||||
title = link_name(r.link);
|
||||
sub = r.link;
|
||||
} else if (r.kind == F_BROWSE) {
|
||||
const BrowseEntry &b = browse_entries[r.index];
|
||||
if (b.dir)
|
||||
icon_folder(x + 22, ry + 26, 28, alpha(C_DIM, a));
|
||||
else if (net_path[0].first.compare(0, 3, "tv:") == 0)
|
||||
icon_tv(x + 22, ry + 26, 28, alpha(C_DIM, a));
|
||||
else if (net_path[0].first.compare(0, 6, "radio:") == 0)
|
||||
icon_radio(x + 22, ry + 26, 28, alpha(C_DIM, a));
|
||||
else
|
||||
icon_film(x + 22, ry + 26, 30, alpha(C_DIM, a));
|
||||
title = b.name;
|
||||
sub = net_path.back().second;
|
||||
} else if (r.kind == F_PLAYLIST) {
|
||||
icon_list(x + 22, ry + 26, 28, alpha(C_DIM, a));
|
||||
title = library_playlists()[r.playlist].name;
|
||||
title = library_playlists()[r.index].name;
|
||||
sub = "Playlist";
|
||||
} else {
|
||||
const MediaItem &m = L()[r.item];
|
||||
const MediaItem &m = L()[r.index];
|
||||
if (m.audio)
|
||||
icon_note(x + 22, ry + 26, 26, alpha(C_DIM, a));
|
||||
else
|
||||
@@ -4456,14 +4583,41 @@ void modal_text_screen()
|
||||
if (search) {
|
||||
Found r = found[found_focus];
|
||||
modal = NONE;
|
||||
if (r.playlist >= 0) {
|
||||
play_playlist(library_playlists()[r.playlist].path);
|
||||
if (r.kind == F_BUSY) {
|
||||
modal = SEARCH; /* nothing to open yet: stay */
|
||||
} else if (r.kind == F_TV || r.kind == F_STATION) {
|
||||
const NetEntry e = (r.kind == F_TV ? net_search_tv() : net_search_radio())[r.index];
|
||||
start_stream(e.url, e.options, false, e.name, net_search_media(e.url));
|
||||
} else if (r.kind == F_SERVER) {
|
||||
/* a place of Browse, opened from its top (○ walks back up) */
|
||||
screen = LIBRARY;
|
||||
screen_fade = 0;
|
||||
tab = BROWSE;
|
||||
net_path.clear();
|
||||
browse_dir.clear();
|
||||
std::string name = r.link;
|
||||
for (const NetServer &sv : net_servers())
|
||||
if (sv.url == r.link)
|
||||
name = sv.name;
|
||||
net_enter(r.link, name);
|
||||
} else if (r.kind == F_LINK) {
|
||||
start_stream(r.link);
|
||||
} else if (r.kind == F_BROWSE) {
|
||||
/* as ✕ on that row of Browse */
|
||||
BrowseEntry e = browse_entries[r.index];
|
||||
browse_focus = r.index;
|
||||
if (e.dir)
|
||||
net_enter(e.path, e.name);
|
||||
else
|
||||
start_stream(e.path, net_login_options(e.path), false, e.name, net_take_media(e.path));
|
||||
} else if (r.kind == F_PLAYLIST) {
|
||||
play_playlist(library_playlists()[r.index].path);
|
||||
} else {
|
||||
std::vector<int> list;
|
||||
for (const Found &x2 : found)
|
||||
if (x2.item >= 0 && (!L()[x2.item].other || x2.item == r.item))
|
||||
list.push_back(x2.item);
|
||||
open_item(r.item, list);
|
||||
if (x2.kind == F_ITEM && (!L()[x2.index].other || x2.index == r.index))
|
||||
list.push_back(x2.index);
|
||||
open_item(r.index, list);
|
||||
}
|
||||
} else {
|
||||
std::string url = links[found_focus];
|
||||
@@ -4621,10 +4775,10 @@ void modal_options()
|
||||
};
|
||||
std::vector<Item> items;
|
||||
if (in_player)
|
||||
items = { { "Resume", OI_RESUME }, { "Audio, subtitles and more", OI_TRACKS },
|
||||
items = { { "Resume", OI_RESUME }, { "Audio, subtitles and more", OI_TRACKS }, { "Search", OI_SEARCH },
|
||||
{ "Send files from a phone or PC", OI_PHONE }, { "Settings", OI_SETTINGS }, { "Stop playback", OI_STOP } };
|
||||
else
|
||||
items = { { "Search", OI_SEARCH }, { "Open a network link", OI_LINK },
|
||||
items = { { "Search", OI_SEARCH },
|
||||
{ "Send files from a phone or PC", OI_PHONE }, { "Settings", OI_SETTINGS },
|
||||
{ "Rescan media", OI_RESCAN } };
|
||||
/* the file in front of you: into a playlist */
|
||||
@@ -4759,13 +4913,14 @@ 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_BDREGION, ST_COUNT
|
||||
ST_WEB, ST_LOOK, ST_OSUB, ST_UILANG, ST_SETUP, ST_BDREGION, ST_HWDEC, ST_MATCH, 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", "Blu-ray region",
|
||||
"Subtitle downloads", "Language", "Run setup again", "Blu-ray region", "Hardware decoding",
|
||||
"Match frame rate",
|
||||
};
|
||||
/* One line under the focused setting: what it does. */
|
||||
const char *const st_help[ST_COUNT] = {
|
||||
@@ -4791,6 +4946,8 @@ const char *const st_help[ST_COUNT] = {
|
||||
"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",
|
||||
"The console's video chip. Off: the processor decodes, slower for 4K",
|
||||
"Films play at their own 24, 25 or 50 Hz: smoother pans. Some TVs go black a moment",
|
||||
};
|
||||
|
||||
/* Categories: an icon, a name, and their settings. */
|
||||
@@ -4799,15 +4956,17 @@ struct SettingsCat {
|
||||
int icon;
|
||||
std::vector<int> rows;
|
||||
};
|
||||
enum { SI_PLAY, SI_SOUND, SI_THEME, SI_LIST, SI_GEAR };
|
||||
enum { SI_PLAY, SI_SOUND, SI_THEME, SI_LIST, SI_GEAR, SI_VIDEO };
|
||||
const SettingsCat settings_cats[] = {
|
||||
{ "Playback", SI_PLAY, { ST_DECODING, ST_ALANG, ST_SLANG, ST_OSUB, ST_BDREGION } },
|
||||
{ "Playback", SI_PLAY, { ST_ALANG, ST_SLANG, ST_OSUB, ST_BDREGION } },
|
||||
{ "Video", SI_VIDEO, { ST_HWDEC, ST_MATCH, ST_DECODING } },
|
||||
{ "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 } },
|
||||
{ "System", SI_GEAR, { ST_WEB, ST_STORAGE, ST_RELOAD, ST_SETUP, ST_DEBUG, ST_ABOUT } },
|
||||
/* (no "Log details" row: the detailed log is for test builds, settings.txt debug=1) */
|
||||
{ "System", SI_GEAR, { ST_WEB, ST_STORAGE, ST_RELOAD, ST_SETUP, ST_ABOUT } },
|
||||
};
|
||||
const int SETTINGS_CATS = 5;
|
||||
const int SETTINGS_CATS = 6;
|
||||
|
||||
/* ---- settings icons: thin outlines, like the tab bar's ---------------------- */
|
||||
|
||||
@@ -4916,6 +5075,7 @@ void category_icon(int icon, float cx, float cy, float s, ImU32 col)
|
||||
case SI_SOUND: icon_speaker(cx, cy, s * 0.9f, col); break;
|
||||
case SI_THEME: si_palette(cx, cy, s, col); break;
|
||||
case SI_LIST: icon_list(cx, cy, s * 0.9f, col); break;
|
||||
case SI_VIDEO: icon_film(cx, cy, s * 0.9f, col); break;
|
||||
default: icon_gear(cx, cy, s * 0.8f, col); break;
|
||||
}
|
||||
}
|
||||
@@ -4923,7 +5083,8 @@ void category_icon(int icon, float cx, float cy, float s, ImU32 col)
|
||||
void setting_icon(int id, float cx, float cy, float s, ImU32 col)
|
||||
{
|
||||
switch (id) {
|
||||
case ST_DECODING: si_chip(cx, cy, s, col); break;
|
||||
case ST_DECODING: case ST_HWDEC: si_chip(cx, cy, s, col); break;
|
||||
case ST_MATCH: icon_speed(cx, cy, s * 0.8f, 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;
|
||||
@@ -4970,6 +5131,8 @@ std::string st_value(int id)
|
||||
static const char *const dec[] = { "Auto (fast when needed)", "Always fast", "Full quality" };
|
||||
switch (id) {
|
||||
case ST_DECODING: return dec[player_decode_mode()];
|
||||
case ST_HWDEC: return player_hw_decoding() ? "On" : "Off";
|
||||
case ST_MATCH: return player_match_rate() ? "On" : "Off";
|
||||
case ST_ALANG: return lang_name(player_audio_language(), false);
|
||||
case ST_SLANG: return lang_name(player_sub_language(), true);
|
||||
case ST_SOUND: return player_audio_output() ? "Surround 5.1 / 7.1" : "Stereo";
|
||||
@@ -5009,6 +5172,12 @@ void st_change(int id, int step)
|
||||
case ST_DECODING:
|
||||
player_set_decode_mode((DecodeMode)((player_decode_mode() + 3 + dir) % 3));
|
||||
break;
|
||||
case ST_MATCH: player_set_match_rate(!player_match_rate()); break;
|
||||
case ST_HWDEC:
|
||||
player_set_hw_decoding(!player_hw_decoding());
|
||||
if (player_active())
|
||||
toast("Hardware decoding: from the next video", 2.5);
|
||||
break;
|
||||
case ST_ALANG: cycle_language(false, dir); break;
|
||||
case ST_SLANG: cycle_language(true, dir); break;
|
||||
case ST_BDREGION: {
|
||||
@@ -5282,7 +5451,7 @@ void modal_speakers()
|
||||
if (hit(PAD_CIRCLE)) {
|
||||
player_speaker_test(-1);
|
||||
modal = SETTINGS;
|
||||
settings_cat = 1;
|
||||
settings_cat = 2; /* Sound */
|
||||
settings_inside = true;
|
||||
modal_focus = 2;
|
||||
return;
|
||||
@@ -6115,7 +6284,12 @@ void compute_video_rect()
|
||||
vid_rect[3] = sh;
|
||||
return;
|
||||
}
|
||||
float aspect = v.aspect > 0 ? v.aspect : 16.0f / 9;
|
||||
/* until the video's info is read (a frame or two after its first
|
||||
* picture), the picture's own shape: 16:9 there stretched a portrait
|
||||
* video over the whole screen for its first frames (it looked like
|
||||
* another video flashing by) */
|
||||
float pic_aspect = video_picture_aspect();
|
||||
float aspect = v.aspect > 0 ? v.aspect : pic_aspect > 0 ? pic_aspect : 16.0f / 9;
|
||||
if (aspect_mode == PIC_16_9)
|
||||
aspect = 16.0f / 9;
|
||||
else if (aspect_mode == PIC_4_3)
|
||||
@@ -7876,7 +8050,8 @@ void ui_frame(const PadState &pad, float frame_dt)
|
||||
mini_player();
|
||||
if (modal == NONE) {
|
||||
bool open_kind = tab == BROWSE && browse_focus < (int)browse_entries.size() &&
|
||||
(browse_entries[browse_focus].dir || browse_entries[browse_focus].net == NET_ADD);
|
||||
(browse_entries[browse_focus].dir || browse_entries[browse_focus].net == NET_ADD ||
|
||||
browse_entries[browse_focus].net == NET_LINK);
|
||||
std::vector<std::pair<uint32_t, const char *>> h = { { PAD_CROSS, open_kind ? "Open" : "Play" } };
|
||||
int fi = focused_item();
|
||||
if (fi >= 0 && L()[fi].other) {
|
||||
|
||||
Reference in new issue
Block a user