From fbd865e93b80f6607313a737c3699507fca4f6a0 Mon Sep 17 00:00:00 2001 From: Scarriffle Date: Tue, 13 Oct 2026 16:03:57 +0200 Subject: [PATCH] Heruntergeladen heisst nicht: ohne Angaben MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bei einer lokalen Datei kehrte der Spieler zurück, sobald sie lief — und liess `item` leer. Daran hing mehr als der Titel: keine Marken, also kein „Intro überspringen"; keine Nachbarfolgen, also keine Knöpfe zum Blättern. Nur bei Heruntergeladenem, und darum sah es so lange nach einem Fehler in der Erkennung aus. Jetzt werden die Angaben nachgeholt, während das Bild schon läuft. Dazu, was derselbe Weg betrifft: der Spieler sagt „Staffel 1 · Folge 6" statt „S1 E6" — der Folgenname wird an einer Stelle gebaut, nicht an dreien. Im Abspann einer Serie stehen zwei Knöpfe statt einem, weil der Abspann zwei Fragen stellt. Und die Abdrücke, mit denen der Tonabgleich geprüft wird, sind jetzt echte: zwei Aufnahmen desselben dreissigsekündigen Stücks, verschiedene Bitraten, durch fpcalc gedreht. Vorher war jede Prüfung mit erfundenen Zahlen bestanden — und genau deshalb war nie belegt, dass der Abgleich an echten Daten etwas findet. Er findet: 17,8–51,0 s und 72,7–105,9 s, bei 20–50 und 75–105. Co-Authored-By: Claude Opus 5 --- Flaro/Views/Player/PlayerModel.swift | 27 ++++++ Flaro/Views/Player/PlayerView.swift | 17 ++-- .../Flaro.Core.Tests/Abdruecke/folgeA.txt | 1 + .../Flaro.Core.Tests/Abdruecke/folgeB.txt | 1 + .../Flaro.Core.Tests/EchteAbdrueckeTests.cs | 56 ++++++++++++ .../Flaro.Core.Tests/Flaro.Core.Tests.csproj | 4 + web/app/src/spieler.ts | 86 ++++++++++++------- web/app/src/style.css | 6 +- web/app/src/views/home.ts | 10 ++- 9 files changed, 167 insertions(+), 41 deletions(-) create mode 100644 server/tests/Flaro.Core.Tests/Abdruecke/folgeA.txt create mode 100644 server/tests/Flaro.Core.Tests/Abdruecke/folgeB.txt create mode 100644 server/tests/Flaro.Core.Tests/EchteAbdrueckeTests.cs diff --git a/Flaro/Views/Player/PlayerModel.swift b/Flaro/Views/Player/PlayerModel.swift index e831ae1..57e4966 100644 --- a/Flaro/Views/Player/PlayerModel.swift +++ b/Flaro/Views/Player/PlayerModel.swift @@ -528,6 +528,18 @@ final class PlayerModel { // nicht erreichbar ist — der eigentliche Sinn des Herunterladens. if let downloads, let localURL = downloads.localURL(forRatingKey: request.ratingKey) { await startLocal(url: localURL) + // **Die Angaben trotzdem holen, nur eben hinterher.** + // + // Hier wurde früher schlicht zurückgekehrt — und damit blieb + // `item` leer. Daran hing mehr, als es aussieht: keine Marken, + // also kein „Intro überspringen"; keine nächste Folge, also + // keine Knöpfe zum Blättern. Bei allem, was heruntergeladen + // war, fehlte das, und nur dort — weshalb es so lange aussah + // wie ein Fehler in der Erkennung. + // + // Nebenher und nicht davor: das Bild soll sofort laufen, und + // ohne Verbindung gibt es ohnehin nichts zu holen. + metadatenNachladen() return } @@ -747,6 +759,21 @@ final class PlayerModel { await load(PlaybackSource(url: url, origin: .remote), decision: decision) } + /// Holt Titelangaben nach, während die lokale Datei schon läuft. + /// + /// Scheitert es — kein Server, kein Netz —, bleibt es, wie es war: die + /// Datei läuft, es gibt bloss keine Marken und keine Nachbarfolgen. + private func metadatenNachladen() { + guard item == nil, let backend else { return } + Task { [weak self] in + guard let geholt = try? await backend.item(id: self?.request.ratingKey ?? "") else { return } + guard let self, self.item == nil else { return } + self.item = geholt + self.updateActiveMarker() + self.preloadNextEpisode() + } + } + private func startLocal(url: URL) async { // Eine Datei auf dem Gerät ist der ganze Film; es gibt keinen // Vorlauf, den der Spieler aufrechnen müsste. diff --git a/Flaro/Views/Player/PlayerView.swift b/Flaro/Views/Player/PlayerView.swift index 71cc860..d3eb375 100644 --- a/Flaro/Views/Player/PlayerView.swift +++ b/Flaro/Views/Player/PlayerView.swift @@ -746,12 +746,15 @@ struct PlayerView: View { Spacer() HStack { Spacer() - // **Im Abspann ist „überspringen" die falsche Frage.** + // **Im Abspann ist „überspringen" die falsche Frage — aber + // nicht die einzige.** // - // Was man dort will, ist die nächste Folge — ans Ende - // dieser zu springen bringt niemanden weiter. Deshalb steht - // derselbe Knopf schon im Abspann, nicht erst, wenn alles - // durchgelaufen ist. + // Was man dort meistens will, ist die nächste Folge; ans + // Ende dieser zu springen bringt niemanden weiter. Wer den + // Abspann aber sehen wollte und es sich anders überlegt, + // soll ihn trotzdem überspringen können. Zwei Antworten, + // zwei Knöpfe — welche gemeint ist, weiss nur der + // Zuschauer. if naechsteAnbieten(model) { Button { if let next = model.nextEpisodeRequest() { @@ -765,7 +768,9 @@ struct PlayerView: View { } .buttonStyle(.borderedProminent) .tint(accent) - } else if let marker = model.activeMarker { + } + + if let marker = model.activeMarker { Button { model.skipActiveMarker() } label: { diff --git a/server/tests/Flaro.Core.Tests/Abdruecke/folgeA.txt b/server/tests/Flaro.Core.Tests/Abdruecke/folgeA.txt new file mode 100644 index 0000000..e5cb507 --- /dev/null +++ b/server/tests/Flaro.Core.Tests/Abdruecke/folgeA.txt @@ -0,0 +1 @@ +4118107519,4118247293,4118239101,4126660477,4126537685,4160092117,4160092117,4126528983,4118270423,3581407703,3581407734,4118245862,4118246886,4117198198,4150779222,4159044950,4158975318,4160021887,4160091487,4151825759,4118271311,3044537663,3044406575,3052786991,3052754223,3052762431,3052725533,3187009373,3187144541,3178678101,3178743765,3176519671,3176527847,3176527735,3176658807,3210213207,3210212695,3210073431,3078083925,3052918141,3052926845,3052795887,3042236399,3075790335,3075757567,3075831263,3075962359,3086382551,3086374869,3052885973,3052754933,3052754421,3035977173,2515891669,2507498967,2507498999,2541188471,2541180279,2541212991,2541081887,2541081951,2549478751,2549609823,2549609823,2516055389,2516047325,2513819645,2513827837,2515926397,2507535743,2507527679,2506478847,2506477791,2507600343,2507600343,2507592663,2507658199,3051878007,3051742847,3051747197,3051683709,3051683325,3043292671,4117091839,4118136319,4151662078,4151703006,4118275294,4118279286,4118270007,4126658839,4118212871,4150784263,4150654261,4150588709,4285920565,3178755895,3187079031,3187070551,3187131991,3053307479,3078411990,3077896182,3044407294,3044505087,3045021087,3045021119,3053286829,3053269485,3044873469,3078427997,3043841311,3052238111,3051840799,3588714271,3588751135,3580221271,3581212119,3581212151,3562394103,3562525141,3579310549,3581375957,3614930389,3614930389,3614930293,3598022007,3606410623,3623122303,3623712094,3590288734,3590321502,3589272926,3588617567,3589657983,3715487743,3715487743,3716019199,3715953661,3715921917,3715458943,3715593591,3715620215,3581398391,4118271317,4118132181,4151686597,4151815637,3889610709,1733794805,1733663221,1695923445,1698020469,1697879093,1697878053,1696698407,1696722991,622982255,622972271,627165551,627169655,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627146103,627144183,1700884983,1700868607,1700869119,1684091902,1692481271,1692483319,3835771511,3852679703,3852679703,3886496263,3886378503,3064292871,3068487175,3086840646,3086832086,3086828022,3053274599,3051181543,3053286903,3053450695,3053385031,3053385029,3052787015,3044398423,3044398175,2507658367,2507633775,2507633791,2541171807,2541032799,2541033325,2549446253,3082253933,3048600285,3049059021,2516374493,2507986943,2503800319,2535314943,2535380447,2535355855,2537448927,2508088799,4127135231,4126496239,4126438895,4126438895,4126528991,4126660047,4126670287,4151820245,4152213493,4152202869,4148008789,4148007733,4146974997,4151177271,4151304231,4151304231,3078509687,3077981559,3044476799,3044482943,3027582591,3035905663,3052682879,3052672831,2511615262,3585369367,3585434967,2516051287,2515981639,2541737287,3613382135,3613382135,3596613111,3596613575,3613382613,3613251573,3581736933,3565020661,3565151701,3565147605,3581920501,3581793653,3615358325,3623874423,3623876183,3623867974,3615348310,3615282038,2541540710,3044406630,3044537463,4118279255,4118279511,4118279517,4118148445,4118148413,4118150461,4118150525,4118279519,4118271327,4118271487,4149598191,4149737470,4158126046,4158093270,3050665974,3042211702,3042211702,3042277174,3052892982,3052892991,3086446879,3086307599,3086308703,3086308733,2549448061,2513927517,2505428445,2505426429,3042301439,3042963935,3051353039,3034575581,3034445565,3034445549,3026055165,3023962109,3040696285,3074380255,3078574559,3078443519,3086840303,3086964205,3053409789,3053409741,2508027341,2508027341,2508158301,2508023165,2549966127,2549970735,2549970751,2533192990,3606967566,3606967646,3598701822,3615475198,3615475166,3581924831,3581867487,3565090271,3565147487,4101887359,4126471485,4126467373,4151633279,3077895551,3061306239,3075920895,3210204143,3176518639,3159750639,3161847165,3161847165,3161849629,3178757917,3178755869,3212269373,3212662077,3212670303,3204281727,3204412927,3203888639,3218561023,3218429919,3184838623,3176462303,3042240511,3042436479,3044472159,3078083679,3086472415,3086480637,3086480637,3086472413,3044406749,3044407261,3027728125,3027719919,3027588727,3027597142,3052696918,3052795254,3052795255,3044538199,3044538183,3044535877,3075864309,3076380645,3086861669,3086861685,3086996767,3086996767,3077434679,3043880247,3045059959,3045023095,3044886005,3044824565,3044889077,3052892567,3050729607,3050730647,3084342518,3077922022,3061079526,3061083622,3061278550,4135020102,4151801366,4125447958,4125390135,4125455679,3588707349,3589690373,3581367573,3581244725,3581241205,3581376309,3581335380,3587621700,3721910093,3721782621,3713394013,3713398077,3612669245,3612734783,3612865919,3587691902,3587659774,3587659502,3579139838,3579139806,3579139806,3714045918,3613251071,4158518783,4158452831,4160549981,4294307965,4294304125,4286046573,4286047213,4252488701,4250387327,4233451391,4233492286,4233493822,4260756862,4294835550,4160617822,4152218958,4150220766,4150228991,3579803645,3571406845,2499102684,2490718172,2490726356,2490886100,2507663188,2516055925,2516046711,2549470007,3061174551,3061117206,3061179702,3061310822,3061314934,3078018551,3044525559,3027617269,3025491125,3025499325,3025630717,4099405309,4126668119,4126668055,4151702807,4150584599,4150649143,4150784823,4118205223,4118267703,3044497237,3044505413,3044504901,3042268501,3042198901,3042237805,3220569453,3220696959,3204317023,3204314911,3204306206,3061699902,3057504574,3057504574,3074421118,3074388310,3042898423,3185505767,3168720373,3168749525,3168229316,3161806148,3161740661,3178525997,3178591549,3178571039,3178702175,3218486654,3219003390,4158588894,4158412494,4158420943,3051157471,3051157503,3051220479,3051224575,3028155871,2507658719,3577206239,4114075999,4151824735,4151701879,4143444263,3069572391,3069572903,3069568375,3075859807,3075990991,3075994831,3042432479,3042432511,3025623023,3027588863,3027588863,3027588735,3027588911,3035985215,3052762399,3052758287,2683692303,2675438943,2675430783,3212170751,3212310013,3220674557,3220670461,3086850045,3086329724,3077940572,3040212316,3040728444,3040826733,3040818277,3045012583,3078559079,3086815590,3086947158,3086955334,2516546374,2516547287,2516416455,2514288583,2514283991,2505964023,2640305655,2642402519,2508182613,2508181589,2541735957,2549608757,2549474661,2666915173,2658527077,2658662357,2675431111,2673333975,2673866326,2673735542,2648494902,2650657078,2650657078,2642243894,2507961639,2491213111,2491415933,2505498101,2513948117,2513951940,2538993092,2539123141,2539114997,2539090917,3579278327,3579278327,3579279350,3587536230,3570695526,3588054390,3588218182,3581869255,3615478983,3615347927,3615356151,3615352295,3615356407,3747469303,3713520503,3715617399,2507657845,2515923509,2515857981,2650076733,2683627117,2673143677,2673213309,2639781887,3210207199,3218601437,3084383709,3084776957,3076313469,3076321663,3044929919,3040802175,3044996991,3028186623,3027598838,3042278902,3042802903,3076347095,3059700951,3068089847,4141839863,4143412597,4151801685,4118247253,4101465941,4101470037,4109751637,4109751669,4107588727,4116501623,4116567415,4118803799,4118734647,3052861239,3052918583,3187005207,3178559319,3176458103,3176523639,3172423511,3172353623,3176548215,3210172271,3220469103,3220451711,3199561807,3203879117,2664912077,2673169917,2673169901 diff --git a/server/tests/Flaro.Core.Tests/Abdruecke/folgeB.txt b/server/tests/Flaro.Core.Tests/Abdruecke/folgeB.txt new file mode 100644 index 0000000..9bb754f --- /dev/null +++ b/server/tests/Flaro.Core.Tests/Abdruecke/folgeB.txt @@ -0,0 +1 @@ +3187635543,2650668375,2650668375,2650668119,3052795223,3052795255,3044398454,3077948278,3077952494,3077960695,3077961207,3077928279,3077928311,3044365687,3044349431,3044505087,3053352927,2516547039,2516407765,2516342229,2514253309,2505897469,2505963005,2541745533,3615487325,3615352157,3614307605,3614438677,3589263639,3581924119,3581924151,3581269815,3581278007,3581275959,3579313495,3612802391,4150254663,4150121687,4150056151,4150130167,4160746983,4160673765,3086997221,3042301685,3042309845,3042309077,3044402037,3044406655,3052795199,3052731775,3052731774,3044408686,3078094206,3078083934,3044529486,3044536655,3044405599,4118106495,4252455023,4252464367,4252448239,4286002687,4294391263,4292302815,4292302815,4292171615,4160091999,4160022047,4294239775,3715491615,3713398559,3713389951,2639615359,2639615343,2513917053,2513917053,2648167773,2648167773,2650264925,2683828061,2683692925,2683693055,2641754103,2641745382,2503329270,2503431638,3040245206,3042407894,3042398711,3052892535,3052761431,3086246229,3086247285,2541024765,2541084157,2541215229,2507595229,2507595213,3052918093,3052787039,2515857791,2507530543,2507536175,2507667239,3078092599,3078092599,3078084439,3078477655,3078616949,3087005685,3078616541,3078608349,3045053917,4118238461,4118115453,4151669887,4159993215,4160056686,4151668606,4151705214,4118273630,4118267614,4118271710,4118139854,4118115150,4118114654,4118114686,4118115686,4118246774,4126635319,3187103029,3187004789,3187012980,3187012948,3218470740,2673203164,2673203197,2673342461,2669150207,3210212863,3178714623,3178714495,3052787039,3187012959,3187013087,3052926815,3086473055,4151825887,4152219135,4152227071,4152226943,4152317055,4118762623,4118762871,4118771575,4127160183,4160190071,4160182231,4160051141,4160083909,4118149077,4118149109,4118149109,3044407285,4151703541,4151703541,4151695223,4151695223,4118107511,4118639990,4118771030,4100879702,4100871638,4101985783,4127151607,4127028597,4126996309,3590125333,3581211925,3581277461,3581269301,3589784885,3623867703,3623843191,3623843191,3615454679,3615315415,3615348215,3581924855,3581933559,4127193047,4126539735,3581208567,3581212151,4151703511,4151702487,4151702399,4151702399,4151703399,4160092007,4160091519,4160224575,4160216093,3078085647,2541737039,3615487071,3615487327,3581900159,4118796287,4126660607,4126660607,3052787711,3052722175,3052729855,3044406615,2490758487,2490725687,2489677183,2489808255,2507634047,2516539391,2549962751,2549962719,2549972959,2508054495,2508053847,3581934935,3590186325,3589662165,3614831061,3614822900,3614949876,3614884341,3614889845,3581310839,3581243255,3580128566,3580132662,3581244790,3589625206,4126659927,4126660439,2516047685,2516047445,2515924596,2541024868,2536830580,2536888149,3040335189,3044537717,3042436583,3050831079,3052920311,3052920279,2507658711,2641876295,2675439447,2675438391,2684221239,2684114215,2674742581,2641327381,2640737557,2641810773,3178747861,4252497815,4124440471,4116051895,4115988391,4115987878,3075863990,3075995094,3075994071,4152217975,4151636343,4150617463,4117194069,4125587317,4125579125,3052881783,3052754294,3044373886,3044373854,3043456270,3051877662,2515006751,2511730047,2499138901,2499073493,2507470837,2503276533,2503342079,2536962047,3078023159,4160222711,4143437271,4143437271,4160214469,4151702997,4118083061,4118671861,4118803445,4118729205,4118790647,4118663399,4118639735,4151141398,4151145494,4151276854,4126113574,4126039606,3589168663,3589101063,3589043719,2515334997,2515916757,2516043733,2507527605,2507470261,2507535797,2541221109,3078092021,3078088021,4151833943,4150785301,4150654261,4150621477,4117189941,4117189911,4117714263,4151268855,4150621687,4151670263,4160058711,3069703510,2683819350,2683688726,2675238710,2641684279,3187009335,3170366839,3170366967,3168261621,3042395637,3042395613,3042400221,2639624191,3713361919,4252329983,4152124799,3615257951,3615315295,3589625183,3589756253,3589764445,3589789519,3589789535,3589658495,3589666671,3614762863,3598054783,3598054878,3598177743,3606496735,3623867903,3581793511,2508059879,2491282933,2491413845,2490889557,2499147572,2515924788,2515859252,2549479220,2549601589,2541215029,2541092117,2507535703,2507666775,3044463991,3044463991,3052918135,3052885463,3044505031,3044505557,2541189093,3077928933,3077863285,3086250839,3086312279,3086313303,2549446014,2515883390,2507503102,2507527646,2507593054,3027686734,3036149598,3052791678,3052725631,3052729725,3077887357,2541081949,2654426444,2671146460,2673309181,2673309677,3185014781,3168106493,2631366621,2513926111,3589789527,3587569495,2549479287,2541024631,3614831991,3581277559,3581408759,3581408759,4116182519,4116182487,4116182471,4116174295,4116174327,4118271477,3581277685,3581408597,3623351637,3623220501,3606439221,3606439221,3606443319,2532693303,2549470575,2549470591,2507658615,2507666903,2490888671,3027752927,3028277215,3162371831,3187472247,3187472239,3221026799,3212670463,3212146143,3212146143,3212277247,3212277247,3187103231,3187144031,3052795231,2505438559,2505438551,2507666774,2507667286,2515990358,3052926806,3086350198,3086276455,3086271847,3077952885,3078092117,3078091861,3073889365,3073897845,3044435317,3044440055,3027720183,3035977695,3036018655,3035953151,3035953151,3052795871,3044374493,3044505565,3045021693,3045021055,3078444399,3077395583,3085792383,3085821023,3085952079,3085956447,3085881693,3045048797,3044921853,3044406253,3044407149,3044537725,3044496671,3043315031,4117069143,4117065079,4117194086,4126635894,4126626630,4126626759,4160091095,4151703543,4151629797,4151629301,4151703029,4151834101,4118271477,3042301269,3042309461,3078087959,3078088471,3086481215,3086473087,3086309247,3077920639,3077899774,3044349438,3044505086,3052893422,3052885502,3052885470,3052762446,2515920718,2541089566,2507666238,2516054847,2515859263,2507470143,2490692927,2490760477,2507594837,2505495877,2513884501,2507658709,2508027893,2508027381,2508027381,2508019189,2550093301,2541704599,2541188567,2524444663,2524313591,2524247543,2541024759,3614832087,3580360151,3580351991,3580351989,3580192245,3580192215,3580195319,2506446191,2514867567,3051741039,3051749214,3051745054,4117100311,4150785815,4150645087,3085291871,3076911485,3078616445,4118795629,4117218685,4117189981,4117198173,4117067101,4125447551,4126504831,4260855646,4260724574,4252335998,2641751934,3044403070,3052795222,3052781910,3052913015,3086471541,3085955349,2548953428,3078419796,3078419828,3078487524,3078586340,3042926565,3050788853,3050788311,3052762583,4151703007,4151694847,4151686647,4151819735,3883384151,3882335559,3848640855,3848648983,1701161015,1680325687,1680315429,1680052261,1696854117,1696845925,1696846949,622976109,627165549,627165559,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627177847,627145079,627146103,627144183,1700868599,1818309111,1818309111,1818309623,1826698999,1826701047,3835771639,3852548855,3886234327,3869719239,4138168005,3065374661,3069506887,3061634391,3078419831,3078481279,3078612855,3078584183,3078575998,3078575998,3061663599,3061663103,3078477087,3053442311,3053384967,2499798293,2491279125,2491250549,2491238261,2491369301,2508150613,2548004181,2682086773,3741184375,4275425399,4275425655,4275557759,4267177343,3193302911,3210079582,3210083678,3172324702,3155547646,3159871966,3159806943,3168162655,3168229245,3184874861,3184819565,3084221821,3084221821,3075833183,3042932046,3042964814,3086996990,3086996990,3086865919,3070088703,3059611037,3059606972,3076388284,3042792860,3045016988,3028178428,3036669949,3036538861,3036542591,3036542527,3027105310,3043817246,3043816766,3043882302,3076912510,3078092126,3078059486,3078084062,3044529630,3044529630,3077957118,3077957086,3078092254,3078084062,3077948927,3044406655,3044926815,3028248397,3027203965,3035453229,3035387693,3035395885,3053314879,3087003935,3060258079,3061273951,4151802975,4151671933,3614801405,3581369853,3587593695,3587593183,3621213007,3614921567,3614822775,3614766439,3614827893,3623350101,3623354325,3579312085,3581401045,3581396935,3581921223,3565143382,3565016422,3564958758,3044859943,3044929591,3077558295,3077567765,3077570389,3078553173,3078553301,3078610645,3086866071,3050624695,3042244534,3042239478,3042206198,3025495526,3027727734,3036116310,3052885271,3053405461,3044889877,2541583637,2541059349,2541186357,2549544245,2549601653,3086349653,3084248021,3075864565,3075987445,3042428917,3050823125,3033923031,3025532407,3025532407,3042308455,3044397422,3044398446,3077924863,3077926911,3078062077,3078049277,3078049277,3086308573,3086316637,3086315613,3086316637,3086382133,3086378295,2541188919,2541180535,2516014710,2516022998,2499237830,2499106758,2507494854,2507503062,3579180534,2505439207,2515861351,2549415765,2515926293,2515860757,2507465781,2507535383,2507591951,2507591967,2549535031,2549609831,2549478775,3052729719,3042244093,3042301437,2506082173,2508179293,2541712989,3074258525,3057481341,3056957309,4151670141,4149704189,4124562653,4107793615,4107793615,4099405151,4099396991,4116043135,4149540207,4149536111,4157993839,4158051167,4158112591,4157921246,4116506622,3042831854,3042967023,3042893311,3042958813,3042833869,3076388301,3076388348,3076519404,3076511724,3075856381,3075864573,3075864317,3077920493,3077920503,3077919583,3078058831,3077927262,3077928238,3084211262,3052885278,3052924190,3036146974,3028286814,3028155727,3026056655,4116567517,4116664829,4158603757,4158603757,4158411245,4150031725 diff --git a/server/tests/Flaro.Core.Tests/EchteAbdrueckeTests.cs b/server/tests/Flaro.Core.Tests/EchteAbdrueckeTests.cs new file mode 100644 index 0000000..dff193b --- /dev/null +++ b/server/tests/Flaro.Core.Tests/EchteAbdrueckeTests.cs @@ -0,0 +1,56 @@ +using Flaro.Core.Media; + +namespace Flaro.Core.Tests; + +/// Der Abgleich an echten Chromaprint-Abdrücken. +/// +/// +/// Bisher nur mit erfundenen Zahlen geprüft. Die sagen, ob die Suche +/// rechnet; sie sagen nicht, ob die Schwellen zu echtem Material passen. +/// Genau dazwischen kann alles liegen: zwei Aufnahmen desselben Stücks sind +/// nie bitgleich, und wie unähnlich sie sind, weiss man erst, wenn man es +/// misst. +/// +/// Die beiden Abdrücke liegen als Prüfdaten daneben: zwei Tonspuren mit +/// demselben dreissigsekündigen Stück an verschiedenen Stellen und in +/// verschiedener Bitrate, durch fpcalc gedreht. Zwanzig Kilobyte +/// Text — der Preis dafür, dass diese Zusage nicht bloss behauptet ist. +/// +public class EchteAbdrueckeTests +{ + private const double JeAbdruck = 4096.0 / 3.0 / 11025.0; + + private static IReadOnlyList? Lesen(string name) + { + var pfad = Path.Combine(TestContext.CurrentContext.TestDirectory, "Abdruecke", name); + if (!File.Exists(pfad)) return null; + + return File.ReadAllText(pfad) + .Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) + .Select(t => unchecked((uint)long.Parse(t))) + .ToList(); + } + + [Test] + public void Dasselbe_Stueck_wird_in_zwei_Aufnahmen_gefunden() + { + var a = Lesen("folgeA.txt"); + var b = Lesen("folgeB.txt"); + if (a is null || b is null) Assert.Ignore("Keine Abdrücke zum Prüfen da."); + + var treffer = Tonabgleich.Finden(a, b, JeAbdruck); + + TestContext.Out.WriteLine($"A {a.Count} Werte, B {b.Count} Werte"); + TestContext.Out.WriteLine(treffer is null + ? "NICHTS GEFUNDEN" + : $"A {treffer.AVon:0.0}–{treffer.ABis:0.0} s, B {treffer.BVon:0.0}–{treffer.BBis:0.0} s"); + + Assert.That(treffer, Is.Not.Null, "das Stück steckt in beiden"); + Assert.Multiple(() => + { + Assert.That(treffer!.AVon, Is.EqualTo(20).Within(3)); + Assert.That(treffer.BVon, Is.EqualTo(75).Within(3)); + Assert.That(treffer.Laenge, Is.EqualTo(30).Within(4)); + }); + } +} diff --git a/server/tests/Flaro.Core.Tests/Flaro.Core.Tests.csproj b/server/tests/Flaro.Core.Tests/Flaro.Core.Tests.csproj index 2dda915..d8ca55e 100644 --- a/server/tests/Flaro.Core.Tests/Flaro.Core.Tests.csproj +++ b/server/tests/Flaro.Core.Tests/Flaro.Core.Tests.csproj @@ -24,4 +24,8 @@ + + + + diff --git a/web/app/src/spieler.ts b/web/app/src/spieler.ts index ce390f0..df60642 100644 --- a/web/app/src/spieler.ts +++ b/web/app/src/spieler.ts @@ -178,7 +178,18 @@ export type Spieleranfrage = { dateiId: string; /** Die Kennung des Titels — dorthin geht der Fortschritt. */ titelId: string; + /** Wie der Titel heisst — bei einem Film reicht das. + * + * **Bei einer Folge nicht.** Dort braucht die Zeile über dem Spieler + * Serie, Staffel, Nummer und Folgentitel, und jede Startstelle hat sie + * anders zusammengesetzt: mal „S1 E6 · Name", mal nur „Wiedergabe", mal + * bloss den Serienamen. Deshalb kommen die Teile jetzt einzeln, und die + * Zeile entsteht genau einmal — hier. */ name: string; + serie?: string | null; + staffel?: number | null; + folge?: number | null; + folgentitel?: string | null; /** Ab welcher Sekunde des Films. */ ab: number; /** Wie lang der Film ist. **Aus der Mediathek, nicht aus dem Strom** — der @@ -274,7 +285,9 @@ export function spieler(anfrage: Spieleranfrage): void { kopf.className = 'pl-kopf'; const name = document.createElement('span'); name.className = 'pl-titel'; - name.textContent = anfrage.name; + name.textContent = anfrage.staffel != null || anfrage.folge != null + ? folgenname(anfrage.serie, anfrage.staffel, anfrage.folge, anfrage.folgentitel) + : anfrage.name; const kopfknoepfe = document.createElement('div'); kopfknoepfe.className = 'pl-gruppe'; @@ -370,10 +383,13 @@ export function spieler(anfrage: Spieleranfrage): void { // die Leiste gar nicht, und ein Knopf, von dem niemand weiss, dass es ihn // gibt, ist keiner. Als eigene Schicht über dem Bild bleibt er sichtbar, // solange die Marke läuft. - const ueberspringen = document.createElement('button'); - ueberspringen.className = 'btn btn-primary pl-ueberspringen hidden'; + // **Zwei Knöpfe, nicht einer.** Im Abspann einer Serie gibt es zwei + // vernünftige Antworten — weiterschauen oder den Abspann überspringen — + // und welche gemeint ist, weiss nur der Zuschauer. + const markenreihe = document.createElement('div'); + markenreihe.className = 'pl-marken hidden'; - schicht.append(video, dreher, mitte, kopf, bedienung, ueberspringen, meldungsfeld, kleinleiste); + schicht.append(video, dreher, mitte, kopf, bedienung, markenreihe, meldungsfeld, kleinleiste); document.body.appendChild(schicht); document.body.classList.add('pl-offen'); @@ -606,13 +622,6 @@ export function spieler(anfrage: Spieleranfrage): void { // // Jetzt stehen sie dort, wo man hinsieht: links und rechts vom Knopf, den // man drückt. - /** Wie die Serie heisst — für die Zeile über dem Spieler. - * - * Aus dem übergebenen Namen gewonnen: wer den Spieler startet, kennt sie, - * der Folgenvertrag nennt sie nicht. Steht dort nichts Brauchbares, - * bleibt die Zeile eben ohne Serie. */ - const serienname = anfrage.name.includes(' · ') ? anfrage.name.split(' · ')[0] : null; - /** Die Nachbarfolgen, sobald der Server sie genannt hat. */ let vorige: Folge | null = null; let naechste: Folge | null = null; @@ -702,7 +711,12 @@ export function spieler(anfrage: Spieleranfrage): void { spieler({ dateiId: datei.id, titelId: ziel.id, - name: folgenname(serienname, ziel.season, ziel.episode, ziel.title), + // Die Serie bleibt dieselbe; sie steht schon im laufenden Wunsch. + name: ziel.title ?? '', + serie: anfrage.serie, + staffel: ziel.season, + folge: ziel.episode, + folgentitel: ziel.title, ab: stelle, laenge: datei.durationSeconds, tonspur: ton, @@ -778,31 +792,45 @@ export function spieler(anfrage: Spieleranfrage): void { * **Keine eigene Uhr.** Die Zeit wird ohnehin bei jedem Fortschreiten * nachgeführt; ein zweiter Zeitgeber dafür liefe neben dem ersten her und * wäre eine zweite Stelle, an der etwas hängen bleibt. */ + /** Was gerade zu tun ist, als ein oder zwei Knöpfe. + * + * **Neu gezeichnet nur, wenn sich etwas ändert.** `zeitAuffrischen` läuft + * mehrmals je Sekunde; die Knöpfe jedesmal neu zu bauen hiesse, einen + * Klick mitten im Drücken zu verlieren. + */ + let markenstandJetzt = ''; + function markenstand(jetzt: number): void { const drin = marken.find(m => jetzt >= m.startSeconds && jetzt < m.endSeconds); - ueberspringen.classList.toggle('hidden', !drin); - if (!drin) return; - - // **Beim Abspann einer Serie ist „überspringen" die falsche Frage.** - // - // Was man dort will, ist die nächste Folge — ans Ende dieser zu - // springen bringt niemanden weiter. Bei einem Film gibt es nichts - // danach; dort heisst es, ihn zu beenden, und der Server hakt ihn - // beim Melden der Stelle ohnehin ab. - const abspann = drin.kind === 'credits'; + const abspann = drin?.kind === 'credits'; const weiter = abspann && naechste?.files[0] != null; + const kennung = `${drin?.startSeconds ?? ''}|${weiter}`; - const text = !abspann ? 'Intro überspringen' - : weiter ? 'Nächste Folge' - : 'Abspann überspringen'; - if (ueberspringen.textContent !== text) ueberspringen.textContent = text; + markenreihe.classList.toggle('hidden', !drin); + if (!drin || kennung === markenstandJetzt) return; + markenstandJetzt = kennung; + markenreihe.replaceChildren(); - ueberspringen.onclick = e => { + // **Im Abspann einer Serie zuerst die nächste Folge.** Das ist, was man + // dort will; „überspringen" steht daneben für den, der den Abspann + // sehen wollte und es sich anders überlegt. + if (weiter) { + const w = knopf('Nächste Folge', 'btn btn-primary'); + w.onclick = e => { e.stopPropagation(); folgeWechseln(naechste); }; + markenreihe.appendChild(w); + } + + const u = knopf( + abspann ? 'Abspann überspringen' : 'Intro überspringen', + weiter ? 'btn' : 'btn btn-primary'); + u.onclick = e => { e.stopPropagation(); - if (weiter) { folgeWechseln(naechste); return; } - if (abspann) { void beenden(); return; } + // Bei einem Film und bei der letzten Folge heisst „überspringen" + // Schluss: ans Ende zu spulen liesse ein stehendes Bild zurück. + if (abspann && !weiter) { void beenden(); return; } void springen(drin.endSeconds); }; + markenreihe.appendChild(u); } /** Ans Ende springen und zumachen — für den Abspann ohne Nachfolger. diff --git a/web/app/src/style.css b/web/app/src/style.css index 77c1589..da40379 100644 --- a/web/app/src/style.css +++ b/web/app/src/style.css @@ -928,13 +928,15 @@ textarea.feld { * Leiste gar nicht. Hier liegt er über dem Bild, rechts über der Stelle, * an der die Leiste erscheinen würde, und bleibt stehen, solange die Marke * läuft. */ -.pl-ueberspringen { +.pl-marken { position: absolute; right: var(--s-section); bottom: calc(var(--s-section) * 3.5); z-index: 3; - box-shadow: 0 2px 12px rgba(0, 0, 0, .6); + display: flex; + gap: var(--s-snug); } +.pl-marken .btn { box-shadow: 0 2px 12px rgba(0, 0, 0, .6); } .pl-unten { display: grid; grid-template-columns: 1fr auto 1fr; diff --git a/web/app/src/views/home.ts b/web/app/src/views/home.ts index 94fad34..2956c48 100644 --- a/web/app/src/views/home.ts +++ b/web/app/src/views/home.ts @@ -2,7 +2,7 @@ import { inhalt, weiterschauen, naechsteFolgen, empfehlungen, saisonal, type Bibliothek, type Angefangen, } from '../api'; -import { leer, fehlerzeile, meldung, folgenname } from '../util'; +import { leer, fehlerzeile, meldung } from '../util'; import { kachel, alsKachel, angefangenAlsKachel, reihe } from '../card'; import { hero, alsHero, angefangenAlsHero, type Heroeintrag } from '../hero'; import { spieler } from '../spieler'; @@ -85,9 +85,11 @@ export function startseite( spieler({ dateiId: e.fileId, titelId: e.id, - name: e.kind === 'episode' - ? folgenname(e.title, e.season, e.episode, e.subTitle) - : e.title, + name: e.title, + serie: e.kind === 'episode' ? e.title : null, + staffel: e.season, + folge: e.episode, + folgentitel: e.subTitle, ab: e.state.positionSeconds, laenge: e.durationSeconds ?? null, danach: () => void aufbauen(),