@vouchfor/embeds 0.0.0-experiment.1b80037 → 0.0.0-experiment.1f98c5e

Sign up to get free protection for your applications and to get access to all the features.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vouchfor/embeds",
3
- "version": "0.0.0-experiment.1b80037",
3
+ "version": "0.0.0-experiment.1f98c5e",
4
4
  "license": "MIT",
5
5
  "author": "Aaron Williams",
6
6
  "main": "dist/es/embeds.js",
@@ -26,21 +26,22 @@
26
26
  "lint:staged": "lint-staged",
27
27
  "prepublishOnly": "yarn build",
28
28
  "size": "size-limit",
29
- "storybook": "yarn prebuild && storybook dev -p 6006",
29
+ "storybook": "yarn prebuild && storybook dev -p 6007",
30
30
  "prebuild": "yarn build:deps && yarn generate:manifest",
31
- "test": "true"
31
+ "test": "rm -rf test/lib && yarn prebuild && vite build --mode test && web-test-runner",
32
+ "test:ci": "yarn test"
32
33
  },
33
34
  "lint-staged": {
34
35
  "**/*.{ts,tsx,js}": "eslint --fix --quiet",
35
36
  "**/*.{md,json,yml}": "prettier --write"
36
37
  },
37
38
  "dependencies": {
38
- "@lit/task": "1.0.0",
39
- "@vouchfor/media-player": "0.0.0-experiment.1b80037",
39
+ "@lit/task": "^1.0.0",
40
+ "@vouchfor/media-player": "0.0.0-experiment.1f98c5e",
40
41
  "uuid": "^9.0.1"
41
42
  },
42
43
  "peerDependencies": {
43
- "lit": "^3.0.2"
44
+ "lit": "^3.1.0"
44
45
  },
45
46
  "devDependencies": {
46
47
  "@esm-bundle/chai": "^4.3.4-fix.0",
@@ -62,13 +63,14 @@
62
63
  "eslint": "^8.50.0",
63
64
  "eslint-plugin-import": "^2.28.1",
64
65
  "lint-staged": "^14.0.1",
65
- "lit": "^2.8.0",
66
+ "lit": "^3.1.0",
66
67
  "prettier": "^3.0.3",
67
68
  "react": "^18.2.0",
68
69
  "react-dom": "^18.2.0",
69
70
  "storybook": "^7.4.5",
70
71
  "typescript": "^5.1.3",
71
72
  "vite": "^4.4.9",
73
+ "vite-plugin-commonjs": "^0.10.0",
72
74
  "vite-plugin-dts": "^3.6.0",
73
75
  "web-component-analyzer": "^1.1.7"
74
76
  }
@@ -0,0 +1,17 @@
1
+ import { expect } from '@esm-bundle/chai';
2
+ // import { fixture } from '@open-wc/testing';
3
+ // import { html } from 'lit';
4
+
5
+ // import type { Embed } from './index.js';
6
+
7
+ // Can't use typescript aliases with esbuild file transforms apparently
8
+ // No idea what a good way to actually build before testing is, the examples give nothing
9
+ // https://modern-web.dev/guides/test-runner/typescript/
10
+ import '../../test/lib/embeds.js';
11
+
12
+ describe('Embeds', () => {
13
+ it('passes', async () => {
14
+ expect(true).to.be.true;
15
+ // const player = await fixture<Embed>(html`<vouch-embed aspectratio=${1}></vouch-embed>`);
16
+ });
17
+ });
@@ -13,13 +13,12 @@ type EmbedArgs = EmbedProps & {
13
13
  const _Embed = ({
14
14
  vouchId,
15
15
  templateId,
16
+ questions,
16
17
  preload,
17
18
  autoplay,
18
19
  env,
19
20
  apiKey,
20
21
  controls,
21
-
22
- resolution,
23
22
  aspectRatio
24
23
  }: EmbedArgs) => {
25
24
  return html`
@@ -27,13 +26,14 @@ const _Embed = ({
27
26
  <vouch-embed
28
27
  env=${ifDefined(env)}
29
28
  apiKey=${ifDefined(apiKey)}
30
- ?autoplay=${autoplay}
31
29
  vouchId=${ifDefined(vouchId)}
32
30
  templateId=${ifDefined(templateId)}
33
- resolution=${ifDefined(resolution)}
34
- aspectRatio=${ifDefined(aspectRatio)}
35
- preload=${ifDefined(preload)}
31
+ .questions=${questions}
36
32
  .controls=${controls}
33
+ ?autoplay=${autoplay}
34
+ preload=${ifDefined(preload)}
35
+ aspectRatio=${ifDefined(aspectRatio)}
36
+ @error=${console.log}
37
37
  ></vouch-embed>
38
38
  </div>
39
39
  `;
@@ -51,11 +51,11 @@ type Story = StoryObj<EmbedArgs>;
51
51
 
52
52
  const Embed: Story = {
53
53
  args: {
54
- env: 'dev',
54
+ env: 'local',
55
55
  apiKey: 'TVik9uTMgE-PD25UTHIS6gyl0hMBWC7AT4dkpdlLBT4VIfDWZJrQiCk6Ak7m1',
56
56
  vouchId: '6JQEIPeStt',
57
- templateId: '7d0113f7-3f9a-4bdd-97e3-07ee6eec5730',
58
- resolution: 1080,
57
+ templateId: '357fc118-e179-4171-9446-ff2b8e9d1b29',
58
+ questions: [],
59
59
  aspectRatio: 0,
60
60
  preload: 'none',
61
61
  autoplay: false
@@ -63,7 +63,7 @@ const Embed: Story = {
63
63
  argTypes: {
64
64
  env: {
65
65
  control: 'radio',
66
- options: ['prod', 'staging', 'dev']
66
+ options: ['local', 'dev', 'staging', 'prod']
67
67
  },
68
68
  preload: {
69
69
  control: 'radio',
@@ -1,7 +1,7 @@
1
1
  import { Task } from '@lit/task';
2
+ import { v4 as uuidv4 } from 'uuid';
2
3
 
3
4
  import type { Embed, EmbedProps } from '..';
4
- import type { TemplateInstance } from '@vouchfor/canvas-video';
5
5
  import type { ReactiveControllerHost } from 'lit';
6
6
  import type { Environment } from '~/utils/env';
7
7
 
@@ -9,7 +9,7 @@ import { getEnvUrls } from '~/utils/env';
9
9
 
10
10
  type EmbedHost = ReactiveControllerHost & Embed;
11
11
 
12
- type TaskDeps = [
12
+ type FetchTaskDeps = [
13
13
  EmbedProps['env'],
14
14
  EmbedProps['apiKey'],
15
15
  EmbedProps['data'],
@@ -17,12 +17,13 @@ type TaskDeps = [
17
17
  EmbedProps['templateId']
18
18
  ];
19
19
 
20
+ type FilterTaskDeps = [EmbedProps['data'], EmbedProps['questions']];
21
+
20
22
  class FetcherController {
21
23
  host: EmbedHost;
22
24
 
23
25
  private _fetching = false;
24
- private _vouch: EmbedProps['data'] | null = null;
25
- private _template: TemplateInstance | null = null;
26
+ private _vouch: EmbedProps['data'];
26
27
 
27
28
  set fetching(value) {
28
29
  if (this._fetching !== value) {
@@ -34,74 +35,94 @@ class FetcherController {
34
35
  return this._fetching;
35
36
  }
36
37
 
37
- set vouch(value) {
38
- if (this._vouch !== value) {
39
- this._vouch = value;
40
- this.host.requestUpdate();
41
- }
42
- }
43
- get vouch() {
44
- return this._vouch;
45
- }
46
-
47
- set template(value) {
48
- if (this._template !== value) {
49
- this._template = value;
50
- this.host.requestUpdate();
51
- }
52
- }
53
- get template() {
54
- return this._template;
55
- }
56
-
57
- private async getVouch(env: Environment, apiKey: string, vouchId?: string) {
38
+ private getVouch = async (env: Environment, apiKey: string, vouchId: string) => {
58
39
  const { embedApiUrl } = getEnvUrls(env);
59
40
 
60
- if (vouchId) {
61
- return fetch(`${embedApiUrl}/vouches/${vouchId}`, {
41
+ const cacheCheck = uuidv4();
42
+ const res = await fetch(`${embedApiUrl}/vouches/${vouchId}`, {
43
+ method: 'GET',
44
+ headers: [
45
+ ['X-Api-Key', apiKey],
46
+ ['X-Cache-Check', cacheCheck]
47
+ ]
48
+ });
49
+
50
+ const vouch = await res.json();
51
+ this.host.dispatchEvent(new CustomEvent('vouch:loaded', { detail: vouch?.id }));
52
+
53
+ // HACK: we're currently using API Gateway caching on the embed API without any invalidation logic,
54
+ // so to ensure that the cache stays up to date, whenever we detect a cache hit we trigger another
55
+ // API call with the `Cache-Control` header which will re-fill the cache
56
+ const resCacheCheck = res?.headers?.get('X-Cache-Check');
57
+ if (resCacheCheck !== cacheCheck) {
58
+ fetch(`${embedApiUrl}/vouches/${vouchId}`, {
62
59
  method: 'GET',
63
- headers: [['X-Api-Key', apiKey]]
64
- }).then((response) => response.json());
60
+ headers: [
61
+ ['X-Api-Key', apiKey],
62
+ ['Cache-Control', 'max-age=0']
63
+ ]
64
+ });
65
65
  }
66
66
 
67
- return null;
68
- }
67
+ return vouch;
68
+ };
69
69
 
70
- private async getTemplate(env: Environment, apiKey: string, templateId?: string) {
70
+ private getTemplate = async (env: Environment, apiKey: string, templateId: string) => {
71
71
  const { embedApiUrl } = getEnvUrls(env);
72
72
 
73
- if (templateId) {
74
- return fetch(`${embedApiUrl}/templates/${templateId}`, {
73
+ const cacheCheck = uuidv4();
74
+ const res = await fetch(`${embedApiUrl}/templates/${templateId}`, {
75
+ method: 'GET',
76
+ headers: [
77
+ ['X-Api-Key', apiKey],
78
+ ['X-Cache-Check', cacheCheck]
79
+ ]
80
+ });
81
+ const template = await res.json();
82
+
83
+ // HACK: we're currently using API Gateway caching on the embed API without any invalidation logic,
84
+ // so to ensure that the cache stays up to date, whenever we detect a cache hit we trigger another
85
+ // API call with the `Cache-Control` header which will re-fill the cache
86
+ const resCacheCheck = res?.headers?.get('X-Cache-Check');
87
+ if (resCacheCheck !== cacheCheck) {
88
+ fetch(`${embedApiUrl}/templates/${templateId}`, {
75
89
  method: 'GET',
76
- headers: [['X-Api-Key', apiKey]]
77
- }).then((response) => response.json());
90
+ headers: [
91
+ ['X-Api-Key', apiKey],
92
+ ['Cache-Control', 'max-age=0']
93
+ ]
94
+ });
78
95
  }
79
96
 
80
- return null;
81
- }
97
+ return template;
98
+ };
82
99
 
83
100
  constructor(host: EmbedHost) {
84
101
  this.host = host;
85
- new Task<TaskDeps, void>(
102
+ new Task<FetchTaskDeps, void>(
86
103
  this.host,
87
- async ([env, apiKey, data, vouchId, templateId]: TaskDeps) => {
104
+ async ([env, apiKey, data, vouchId, templateId]: FetchTaskDeps) => {
88
105
  try {
89
- this.vouch = null;
90
- this.template = null;
106
+ host.vouch = undefined;
107
+ host.template = undefined;
91
108
 
92
109
  if (data) {
93
- const template = await this.getTemplate(env, apiKey, templateId);
94
- this.vouch = data;
95
- this.template = data?.settings?.template?.instance ?? template;
110
+ let template;
111
+ if (templateId) {
112
+ this.fetching = true;
113
+ template = await this.getTemplate(env, apiKey, templateId);
114
+ }
115
+ this._vouch = data;
116
+ host.template = template ?? data?.settings?.template?.instance;
96
117
  } else if (vouchId) {
97
118
  this.fetching = true;
98
119
 
99
120
  const [vouch, template] = await Promise.all([
100
121
  this.getVouch(env, apiKey, vouchId),
101
- this.getTemplate(env, apiKey, templateId)
122
+ templateId ? this.getTemplate(env, apiKey, templateId) : null
102
123
  ]);
103
- this.vouch = vouch;
104
- this.template = vouch?.settings?.template?.instance ?? template;
124
+ this._vouch = vouch;
125
+ host.template = template ?? vouch?.settings?.template?.instance;
105
126
  }
106
127
  } finally {
107
128
  this.fetching = false;
@@ -109,6 +130,22 @@ class FetcherController {
109
130
  },
110
131
  () => [host.env, host.apiKey, host.data, host.vouchId, host.templateId]
111
132
  );
133
+
134
+ // This second task is to be able to filter the vouch without fetching it again if only the questions changed
135
+ new Task<FilterTaskDeps, void>(
136
+ this.host,
137
+ ([vouch, questions]: FilterTaskDeps) => {
138
+ host.vouch = vouch
139
+ ? {
140
+ ...vouch,
141
+ questions: {
142
+ items: vouch?.questions.items.filter((_, index) => !questions?.length || questions?.includes(index + 1))
143
+ }
144
+ }
145
+ : undefined;
146
+ },
147
+ () => [this._vouch, host.questions]
148
+ );
112
149
  }
113
150
  }
114
151
 
@@ -1,18 +1,20 @@
1
+ import { TEMPLATE_VERSION } from '@vouchfor/canvas-video';
1
2
  import { v4 as uuidv4 } from 'uuid';
2
3
 
3
4
  import type { Embed } from '..';
4
5
  import type { VideoEventDetail } from '@vouchfor/media-player';
5
6
  import type { ReactiveController, ReactiveControllerHost } from 'lit';
6
7
 
8
+ import packageJson from '../../../../package.json';
7
9
  import { getEnvUrls } from '~/utils/env';
8
10
 
9
- const STREAMED_THROTTLE = 2000;
11
+ const MINIMUM_SEND_THRESHOLD = 1;
10
12
 
11
13
  type EmbedHost = ReactiveControllerHost & Embed;
12
14
 
13
15
  type TrackingEvent = 'VOUCH_LOADED' | 'VOUCH_RESPONSE_VIEWED' | 'VIDEO_PLAYED' | 'VIDEO_STREAMED';
14
16
  type TrackingPayload = {
15
- vouchId: string;
17
+ vouchId?: string;
16
18
  answerId?: string;
17
19
  streamStart?: number;
18
20
  streamEnd?: number;
@@ -36,21 +38,23 @@ class TrackingController implements ReactiveController {
36
38
  private _hasPlayed = false;
37
39
  private _hasLoaded: BooleanMap = {};
38
40
  private _answersViewed: BooleanMap = {};
39
- private _streamedTime: TimeMap = {};
40
- private _streamedPrevTimestamp: TimeMap = {};
41
+ private _streamStartTime: TimeMap = {};
42
+ private _streamLatestTime: TimeMap = {};
43
+ private _currentlyPlayingVideo: VideoEventDetail | null = null;
41
44
 
42
45
  constructor(host: EmbedHost) {
43
46
  this.host = host;
44
47
  host.addController(this);
45
48
  }
46
49
 
47
- private _findVouchId() {
48
- if (this.host.data) {
49
- if ('uuid' in this.host.data) {
50
- return this.host.data.uuid;
51
- }
52
- return this.host.data.id;
50
+ private _findVouchId(payload?: TrackingPayload) {
51
+ if (payload && 'vouchId' in payload) {
52
+ return payload.vouchId;
53
+ }
54
+ if (this.host.vouch) {
55
+ return this.host.vouch.id;
53
56
  }
57
+ return null;
54
58
  }
55
59
 
56
60
  private _createVisitor = (visitorId: string) => {
@@ -116,7 +120,7 @@ class TrackingController implements ReactiveController {
116
120
  });
117
121
 
118
122
  return {
119
- source: 'media_player',
123
+ source: this.host.trackingSource,
120
124
  time: new Date(),
121
125
  region,
122
126
  country,
@@ -124,20 +128,31 @@ class TrackingController implements ReactiveController {
124
128
  screenWidth: window.screen.width,
125
129
  referrer: document.referrer,
126
130
  currentUrl: location.href,
131
+ embedType: 'media-player-embed',
132
+ embedVersion: packageJson.version,
133
+ templateVersion: TEMPLATE_VERSION,
127
134
  ...utmParams
128
135
  };
129
136
  };
130
137
 
131
- private _sendTrackingEvent = (event: TrackingEvent, payload: TrackingPayload) => {
138
+ private _sendTrackingEvent = (event: TrackingEvent, payload?: TrackingPayload) => {
139
+ const vouchId = this._findVouchId(payload);
140
+
141
+ if (!vouchId || this.host.disableTracking) {
142
+ return;
143
+ }
144
+
132
145
  const { publicApiUrl } = getEnvUrls(this.host.env);
133
146
  const { client, tab, request, visitor } = this._getUids();
134
147
 
135
- // Don't send tracking if we don't have a source
136
148
  navigator.sendBeacon(
137
- `${publicApiUrl}/api/events`,
149
+ `${publicApiUrl}/api/v2/events`,
138
150
  JSON.stringify({
139
151
  event,
140
- payload,
152
+ payload: {
153
+ ...payload,
154
+ vouchId
155
+ },
141
156
  context: {
142
157
  'x-uid-client': client,
143
158
  'x-uid-tab': tab,
@@ -149,6 +164,26 @@ class TrackingController implements ReactiveController {
149
164
  );
150
165
  };
151
166
 
167
+ private _streamEnded = () => {
168
+ if (this._currentlyPlayingVideo) {
169
+ const { id, key } = this._currentlyPlayingVideo;
170
+ // Don't send a tracking event when seeking backwards
171
+ if (this._streamLatestTime[key] > this._streamStartTime[key] + MINIMUM_SEND_THRESHOLD) {
172
+ // Send a video streamed event any time the stream ends to capture the time between starting
173
+ // the video and the video stopping for any reason (pausing, deleting the embed node or closing the browser)
174
+ this._sendTrackingEvent('VIDEO_STREAMED', {
175
+ answerId: id,
176
+ streamStart: this._streamStartTime[key],
177
+ streamEnd: this._streamLatestTime[key]
178
+ });
179
+ }
180
+
181
+ // Make sure these events are only sent once by deleting the start and latest times
182
+ delete this._streamStartTime[key];
183
+ delete this._streamLatestTime[key];
184
+ }
185
+ };
186
+
152
187
  private _handleVouchLoaded = ({ detail: vouchId }: CustomEvent<string>) => {
153
188
  if (!vouchId) {
154
189
  return;
@@ -156,91 +191,84 @@ class TrackingController implements ReactiveController {
156
191
 
157
192
  // Only send loaded event once per session
158
193
  if (!this._hasLoaded[vouchId]) {
159
- this._sendTrackingEvent('VOUCH_LOADED', {
160
- vouchId
161
- });
194
+ this._sendTrackingEvent('VOUCH_LOADED', { vouchId });
162
195
  this._hasLoaded[vouchId] = true;
163
196
  }
164
197
  };
165
198
 
166
199
  private _handlePlay = () => {
167
- const vouchId = this._findVouchId();
168
-
169
- if (!vouchId) {
170
- return;
171
- }
172
-
173
200
  // Only send the video played event once per session
174
201
  if (!this._hasPlayed) {
175
202
  this._sendTrackingEvent('VIDEO_PLAYED', {
176
- vouchId,
177
203
  streamStart: this.host.currentTime
178
204
  });
179
205
  this._hasPlayed = true;
180
206
  }
181
207
  };
182
208
 
183
- private _handleVideoPlay = ({ detail: { id, node } }: CustomEvent<VideoEventDetail>) => {
184
- const vouchId = this._findVouchId();
185
- if (!vouchId) {
186
- return;
187
- }
209
+ private _handleVideoPlay = ({ detail: { id, key, node } }: CustomEvent<VideoEventDetail>) => {
188
210
  // Only increment play count once per session
189
- if (!this._answersViewed[id]) {
211
+ if (!this._answersViewed[key]) {
190
212
  this._sendTrackingEvent('VOUCH_RESPONSE_VIEWED', {
191
- vouchId,
192
213
  answerId: id
193
214
  });
194
- this._answersViewed[id] = true;
215
+ this._answersViewed[key] = true;
195
216
  }
196
- this._streamedTime[id] = node.currentTime;
197
- this._streamedPrevTimestamp[id] = Date.now();
198
- };
199
217
 
200
- private _handleVideoTimeUpdate = ({ detail: { id, node } }: CustomEvent<VideoEventDetail>) => {
201
- const vouchId = this._findVouchId();
202
- if (!vouchId) {
203
- return;
218
+ if (!this._streamStartTime[key]) {
219
+ this._streamStartTime[key] = node.currentTime;
220
+ this._streamLatestTime[key] = node.currentTime;
204
221
  }
205
- const currentTimestamp = Date.now();
222
+ };
223
+
224
+ private _handleVideoTimeUpdate = ({ detail: { id, key, node } }: CustomEvent<VideoEventDetail>) => {
206
225
  if (
226
+ // We only want to count any time that the video is actually playing
207
227
  !this.host.paused &&
208
- // Only fire the video seeked event when this video is the active one
209
- id === this.host.scene?.video?.id &&
210
- // Throttle the frequency that we send streamed events while playing
211
- currentTimestamp - this._streamedPrevTimestamp[id] > STREAMED_THROTTLE
228
+ // Only update the latest time if this event fires for the currently active video
229
+ id === this.host.scene?.video?.id
212
230
  ) {
231
+ this._currentlyPlayingVideo = { id, key, node };
232
+ this._streamLatestTime[key] = node.currentTime;
233
+ }
234
+ };
235
+
236
+ private _handleVideoPause = ({ detail: { id, key } }: CustomEvent<VideoEventDetail>) => {
237
+ if (this._streamLatestTime[key] > this._streamStartTime[key] + MINIMUM_SEND_THRESHOLD) {
213
238
  this._sendTrackingEvent('VIDEO_STREAMED', {
214
- vouchId,
215
239
  answerId: id,
216
- streamStart: this._streamedTime[id],
217
- streamEnd: node.currentTime
240
+ streamStart: this._streamStartTime[key],
241
+ streamEnd: this._streamLatestTime[key]
218
242
  });
219
- this._streamedTime[id] = node.currentTime;
220
- this._streamedPrevTimestamp[id] = currentTimestamp;
221
243
  }
244
+ delete this._streamStartTime[key];
245
+ delete this._streamLatestTime[key];
222
246
  };
223
247
 
224
- private _handleVideoPause = ({ detail: { id, node } }: CustomEvent<VideoEventDetail>) => {
225
- const vouchId = this._findVouchId();
226
- if (!vouchId) {
227
- return;
248
+ private _pageUnloading = () => {
249
+ this._streamEnded();
250
+ // This will try to send the same stream event again so we delete the start and latest
251
+ // time in stream ended so that there is no times to send and the pause event does nothing
252
+ this.host.pause();
253
+ };
254
+
255
+ private _handleVisibilityChange = () => {
256
+ if (document.visibilityState === 'hidden') {
257
+ this._pageUnloading();
228
258
  }
229
- // Send a video streamed event any time the video pauses then reset the streamed state
230
- // We do this to capture the last bit of time that the video was played between the previous
231
- // stream event and the video being paused manually or stopping because it ended
232
- this._sendTrackingEvent('VIDEO_STREAMED', {
233
- vouchId,
234
- answerId: id,
235
- streamStart: this._streamedTime[id],
236
- streamEnd: node.currentTime
237
- });
238
- delete this._streamedTime[id];
239
- delete this._streamedPrevTimestamp[id];
259
+ };
260
+
261
+ private _handlePageHide = () => {
262
+ this._pageUnloading();
240
263
  };
241
264
 
242
265
  hostConnected() {
243
266
  requestAnimationFrame(() => {
267
+ if ('onvisibilitychange' in document) {
268
+ document.addEventListener('visibilitychange', this._handleVisibilityChange);
269
+ } else {
270
+ window.addEventListener('pagehide', this._handlePageHide);
271
+ }
244
272
  this.host.addEventListener('vouch:loaded', this._handleVouchLoaded);
245
273
  this.host.mediaPlayer?.addEventListener('play', this._handlePlay);
246
274
  this.host.mediaPlayer?.addEventListener('video:play', this._handleVideoPlay);
@@ -250,6 +278,12 @@ class TrackingController implements ReactiveController {
250
278
  }
251
279
 
252
280
  hostDisconnected() {
281
+ this._streamEnded();
282
+ if ('onvisibilitychange' in document) {
283
+ document.removeEventListener('visibilitychange', this._handleVisibilityChange);
284
+ } else {
285
+ window.removeEventListener('pagehide', this._handlePageHide);
286
+ }
253
287
  this.host.removeEventListener('vouch:loaded', this._handleVouchLoaded);
254
288
  this.host.mediaPlayer?.removeEventListener('play', this._handlePlay);
255
289
  this.host.mediaPlayer?.removeEventListener('video:play', this._handleVideoPlay);