Repository: shaka-project/shaka-player
Pull request #8275: fix: Update region observer with new rules for small regions
Author: login: avelad
Labels: name: type: bug
description: Something isn't working correctly
name: status: archived
description: Archived and locked; will not be updated
name: priority: P1
description: Big impact or workaround impractical; resolve before feature release
Created: 2025-03-14T11:44:50Z | Merged: 2025-03-14T13:01:28Z

This is necessary because some EMSG and ID3 events might not fire because the endTime is equal to or very close to the startTime. 

Related to https://github.com/shaka-project/shaka-player/pull/8012


Diff:
diff --git a/lib/media/region_observer.js b/lib/media/region_observer.js
index d6ffa7673f..a519378e28 100644
--- a/lib/media/region_observer.js
+++ b/lib/media/region_observer.js
@@ -96,7 +96,16 @@ shaka.media.RegionObserver = class extends shaka.util.FakeEventTarget {
       {
         weWere: BEFORE_THE_REGION,
         weAre: AFTER_THE_REGION,
-        invoke: (region, seeking) => this.onEvent_('skip', region, seeking),
+        invoke: (region, seeking) => {
+          if (seeking) {
+            this.onEvent_('skip', region, seeking);
+          } else {
+            // This is the case of regions whose duration is smaller than the
+            // resolution of our polling.
+            this.onEvent_('enter', region, seeking);
+            this.onEvent_('exit', region, seeking);
+          }
+        },
       },
       {
         weWere: AFTER_THE_REGION,
diff --git a/test/media/region_observer_unit.js b/test/media/region_observer_unit.js
index fd9c43627c..d572c3514e 100644
--- a/test/media/region_observer_unit.js
+++ b/test/media/region_observer_unit.js
@@ -113,25 +113,29 @@ describe('RegionObserver', () => {
     expect(onExitRegion).toHaveBeenCalledOnceMoreWith([region, false]);
   });
 
-  it('fires skip event when we enter and leave region in one move', () => {
-    const region = createRegion('my-region', 5, 10);
-    timeline.addRegion(region);
-
-    // Make sure we are before the region starts.
-    poll(observer,
-        /* timeInSeconds= */ 4,
-        /* seeking= */ false);
-    expect(onSkipRegion).not.toHaveBeenCalled();
-
-    // Make sure we call |onSkip| when we move so far that we skip over the
-    // region.
-    poll(observer,
-        /* timeInSeconds= */ 15,
-        /* seeking= */ false);
-    expect(onSkipRegion).toHaveBeenCalledOnceMoreWith([region, false]);
-  });
-
-  it('fires skip events for zero-duration regions', () => {
+  it('fires start and end events when we enter and leave region in one move',
+      () => {
+        const region = createRegion('my-region', 5, 10);
+        timeline.addRegion(region);
+
+        // Make sure we are before the region starts.
+        poll(observer,
+            /* timeInSeconds= */ 4,
+            /* seeking= */ false);
+        expect(onEnterRegion).not.toHaveBeenCalled();
+        expect(onExitRegion).not.toHaveBeenCalled();
+        expect(onSkipRegion).not.toHaveBeenCalled();
+
+        // Make sure we call |onEnter| and |onExit|
+        poll(observer,
+            /* timeInSeconds= */ 15,
+            /* seeking= */ false);
+        expect(onEnterRegion).toHaveBeenCalledOnceMoreWith([region, false]);
+        expect(onExitRegion).toHaveBeenCalledOnceMoreWith([region, false]);
+        expect(onSkipRegion).not.toHaveBeenCalled();
+      });
+
+  it('fires start and end events for zero-duration regions', () => {
     // Make a region with no duration.
     const region = createRegion('my-region', 5, 5);
     timeline.addRegion(region);
@@ -140,14 +144,17 @@ describe('RegionObserver', () => {
     poll(observer,
         /* timeInSeconds= */ 4,
         /* seeking= */ false);
+    expect(onEnterRegion).not.toHaveBeenCalled();
+    expect(onExitRegion).not.toHaveBeenCalled();
     expect(onSkipRegion).not.toHaveBeenCalled();
 
-    // Make sure we call |onSkip| when we move so far that we skip over the
-    // region.
+    // Make sure we call |onEnter| and |onExit|
     poll(observer,
         /* timeInSeconds= */ 10,
         /* seeking= */ false);
-    expect(onSkipRegion).toHaveBeenCalledOnceMoreWith([region, false]);
+    expect(onEnterRegion).toHaveBeenCalledOnceMoreWith([region, false]);
+    expect(onExitRegion).toHaveBeenCalledOnceMoreWith([region, false]);
+    expect(onSkipRegion).not.toHaveBeenCalled();
   });
 
   // We want to simulate a "normal" case of overlapping regions. For this we


Reviews:
user: login: tykus160
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-14T12:25:05Z
user: login: avelad
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-14T12:30:15Z
user: login: tykus160
body: Perfecto!
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-14T12:31:49Z

Review comments:
diff_hunk: @@ -96,6 +96,18 @@ shaka.media.RegionObserver = class extends shaka.util.FakeEventTarget {
       {
         weWere: BEFORE_THE_REGION,
         weAre: AFTER_THE_REGION,
+        wasSeeking: false,
+        invoke: (region, seeking) => {
+          // This is the case of regions whose duration is smaller than the
+          // resolution of our polling.
+          this.onEvent_('enter', region, seeking);
+          this.onEvent_('exit', region, seeking);
+        },
path: lib/media/region_observer.js
user: login: tykus160
body: Maybe instead of new rule, change invoke implementation?
```suggestion
        invoke: (region, seeking) => {
          if (seeking) {
            this.onEvent_('skip', region, seeking);
          } else {
            // This is the case of regions whose duration is smaller than the
            // resolution of our polling.
            this.onEvent_('enter', region, seeking);
            this.onEvent_('exit', region, seeking);
          }
        },
```
created_at: 2025-03-14T12:24:35Z
author_association: MEMBER
diff_hunk: @@ -96,6 +96,18 @@ shaka.media.RegionObserver = class extends shaka.util.FakeEventTarget {
       {
         weWere: BEFORE_THE_REGION,
         weAre: AFTER_THE_REGION,
+        wasSeeking: false,
+        invoke: (region, seeking) => {
+          // This is the case of regions whose duration is smaller than the
+          // resolution of our polling.
+          this.onEvent_('enter', region, seeking);
+          this.onEvent_('exit', region, seeking);
+        },
path: lib/media/region_observer.js
user: login: avelad
body: Done!
created_at: 2025-03-14T12:30:15Z
author_association: MEMBER

PR comments:
user: login: shaka-bot
created_at: 2025-03-14T12:09:12Z
body: Incremental code coverage: 87.50%
author_association: COLLABORATOR
user: login: avelad
created_at: 2025-03-14T12:33:03Z
body: > Perfecto!

Dziękuję!
author_association: MEMBER