summaryrefslogtreecommitdiff
path: root/server/internal
diff options
context:
space:
mode:
Diffstat (limited to 'server/internal')
-rw-r--r--server/internal/skips/kinds.go51
-rw-r--r--server/internal/skips/kinds_test.go27
2 files changed, 69 insertions, 9 deletions
diff --git a/server/internal/skips/kinds.go b/server/internal/skips/kinds.go
index e70ce05..e2518dd 100644
--- a/server/internal/skips/kinds.go
+++ b/server/internal/skips/kinds.go
@@ -38,10 +38,10 @@ const (
// confirm anything until a second fetch occasion has passed.
func Classify(breaks []AdBreak, servedMs, declaredMs int64) []AdBreak {
out := make([]AdBreak, 0, len(breaks))
- // If there's no declared length, we can't compare, so treat conservatively as inserted ads.
+ // If we can't judge (missing served or declared length), treat conservatively as inserted ads.
// If both lengths are known and the file is longer than declared by more than encoding slop,
// it contains inserted ads.
- inserted := declaredMs <= 0 || (servedMs > 0 && declaredMs > 0 && servedMs-declaredMs > durationSlopMs)
+ inserted := servedMs <= 0 || declaredMs <= 0 || (servedMs > 0 && declaredMs > 0 && servedMs-declaredMs > durationSlopMs)
for _, b := range breaks {
b.Kind = KindAd
@@ -53,15 +53,48 @@ func Classify(breaks []AdBreak, servedMs, declaredMs int64) []AdBreak {
return out
}
-// furnitureKindAt names a break by where it sits. Opening first: a very short episode could
-// satisfy both tests, and its first break is its opening.
+// furnitureKindAt names a break by where it sits.
+//
+// For normal-length episodes, the opening and closing windows don't overlap. For very short
+// episodes, they do: a break may be close to both the start and the end. In that case, classify
+// by the nearest edge.
func furnitureKindAt(b AdBreak, servedMs int64) BreakKind {
- switch {
- case b.StartMs <= edgeWindowMs:
+ if servedMs <= 0 {
+ return KindCue // shouldn't happen but be safe
+ }
+
+ // Use a window size proportional to the episode length for very short episodes.
+ // For long episodes (>= 240s), use the fixed edgeWindowMs (120s).
+ // For short episodes, use a quarter of the episode length as the window.
+ windowMs := int64(edgeWindowMs)
+ if servedMs < 2*edgeWindowMs {
+ // For short episodes (< 240s), use a quarter of the episode as the window
+ windowMs = servedMs / 4
+ }
+
+ distToStart := b.StartMs
+ distToEnd := servedMs - b.EndMs
+
+ inOpeningWindow := distToStart <= windowMs
+ inClosingWindow := distToEnd <= windowMs
+
+ if inOpeningWindow && inClosingWindow {
+ // In the overlap - use nearest edge
+ if distToStart < distToEnd {
+ return KindIntro
+ } else if distToEnd < distToStart {
+ return KindOutro
+ } else {
+ // Exactly in the middle - treat as cue (interior break)
+ return KindCue
+ }
+ }
+
+ if inOpeningWindow {
return KindIntro
- case servedMs > 0 && servedMs-b.EndMs <= edgeWindowMs:
+ }
+ if inClosingWindow {
return KindOutro
- default:
- return KindCue
}
+ return KindCue
}
diff --git a/server/internal/skips/kinds_test.go b/server/internal/skips/kinds_test.go
index f9fc6f0..544b9ea 100644
--- a/server/internal/skips/kinds_test.go
+++ b/server/internal/skips/kinds_test.go
@@ -109,3 +109,30 @@ func TestClassifyLeavesEverythingButTheKindAlone(t *testing.T) {
t.Errorf("classify altered the range: %+v, want %+v with only Kind set", got, in)
}
}
+
+// Finding 1: A zero or invalid servedMs falls through to furniture instead of the mandated "ad" fallback.
+func TestAZeroServedLengthIsTreatedAsAdvertising(t *testing.T) {
+ got := Classify([]AdBreak{{StartMs: 50_000, EndMs: 55_000}}, 0, 100_000)
+
+ if got[0].Kind != KindAd {
+ t.Errorf("kind %q, want %q when served length is zero", got[0].Kind, KindAd)
+ }
+}
+
+// Finding 2: In a short episode, interior and closing breaks are misclassified as "intro".
+func TestAShortEpisodeClassifiesBreaksByPosition(t *testing.T) {
+ // Episode is 100 seconds (less than 2*edgeWindowMs = 240s).
+ // With breaks at 0s, 50s, and 90s, they should be distinguished as intro, cue, and outro.
+ got := Classify([]AdBreak{
+ {StartMs: 0, EndMs: 5_000},
+ {StartMs: 50_000, EndMs: 55_000},
+ {StartMs: 90_000, EndMs: 95_000},
+ }, 100_000, 100_000)
+
+ want := []BreakKind{KindIntro, KindCue, KindOutro}
+ for i, k := range kindsOf(got) {
+ if k != want[i] {
+ t.Errorf("break %d at %dms is %q, want %q in short episode", i, got[i].StartMs, k, want[i])
+ }
+ }
+}