107 |
Sample VAST with Click Tracking
108 |
109 | HLS, interstitial@5sec, 1 ad parsed form
110 | this Google IMA VAST example
111 |
112 |
113 |
114 |
115 |
190 |
--------------------------------------------------------------------------------
/public/samples/sample-ad-click/main.m3u8:
--------------------------------------------------------------------------------
1 | #EXTM3U
2 | #EXT-X-DEFINE:QUERYPARAM="user"
3 | #EXT-X-DEFINE:QUERYPARAM="content"
4 |
5 | #EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="stereo",LANGUAGE="en",NAME="English",DEFAULT=YES,AUTOSELECT=YES,URI="audio/stereo/en/128kbit.m3u8"
6 | #EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="stereo",LANGUAGE="dubbing",NAME="Dubbing",DEFAULT=NO,AUTOSELECT=YES,URI="audio/stereo/none/128kbit.m3u8"
7 | #EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="surround",LANGUAGE="dubbing",NAME="Dubbing",DEFAULT=NO,AUTOSELECT=YES,URI="audio/stereo/none/128kbit.m3u8"
8 |
9 | #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=831270,CODECS="avc1.4d4015,mp4a.40.2",AUDIO="stereo",RESOLUTION=638x272
10 | video/800kbit.m3u8?user={$user}&content={$content}
11 |
12 | #EXT-X-ENDLIST
13 |
14 |
15 |
--------------------------------------------------------------------------------
/public/samples/sample-ad-signaling/index.html:
--------------------------------------------------------------------------------
1 |
2 |
Sample VAST 1
3 |
4 |
5 |
68 |
69 |
70 |
71 |
85 |
86 |
Sample VAST with Tracking Events
87 |
88 | HLS, interstitial@5sec, 1 ad parsed form
89 | this Google IMA VAST example
90 |
91 |
92 |
93 |
94 |
95 |
96 |
171 |
172 |
--------------------------------------------------------------------------------
/public/samples/sample-ad-signaling/main.m3u8:
--------------------------------------------------------------------------------
1 | #EXTM3U
2 | #EXT-X-DEFINE:QUERYPARAM="user"
3 | #EXT-X-DEFINE:QUERYPARAM="content"
4 |
5 | #EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="stereo",LANGUAGE="en",NAME="English",DEFAULT=YES,AUTOSELECT=YES,URI="audio/stereo/en/128kbit.m3u8"
6 | #EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="stereo",LANGUAGE="dubbing",NAME="Dubbing",DEFAULT=NO,AUTOSELECT=YES,URI="audio/stereo/none/128kbit.m3u8"
7 | #EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="surround",LANGUAGE="dubbing",NAME="Dubbing",DEFAULT=NO,AUTOSELECT=YES,URI="audio/stereo/none/128kbit.m3u8"
8 |
9 | #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=831270,CODECS="avc1.4d4015,mp4a.40.2",AUDIO="stereo",RESOLUTION=638x272
10 | video/800kbit.m3u8?user={$user}&content={$content}
11 |
12 | #EXT-X-ENDLIST
13 |
14 |
15 |
--------------------------------------------------------------------------------
/public/samples/sample-api-jwt/index.html:
--------------------------------------------------------------------------------
1 |
2 |
Sample API 1
3 |
4 |
71 |
72 |
73 |
74 |
88 |
89 |
Sample API JWT
90 |
91 | HLS, interstitial@5sec, 1 ad parsed form
92 | this Google IMA VAST example
93 |
94 |
95 | JWT Secret:
96 | 251450bcd828e761eca58b28a4e15e9aa8fdc7e42579e8f9492303d0bbd10b21
97 |
98 |
99 | JWT:
100 | eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1cmwiOiJodHRwOi8vbG9jYWxob3N0OjMwMDAvc2FtcGxlcy9zYW1wbGUtdmFzdC0xL3Zhc3Qtc2FtcGxlLnhtbCIsImlhdCI6MTczMzc3MzcxMiwiZXhwIjoyMDQ5MzQ5NzEyfQ.iliLPo-KgB4mCH6mLTrtGUtDy28dN_zZYDWV_xwU5sA
101 |
102 |
103 | Use jwt.io to decode the JWT
104 |
105 |
106 | API Call:
107 | try out
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
191 |
192 |
--------------------------------------------------------------------------------
/public/samples/sample-api-jwt/main.m3u8:
--------------------------------------------------------------------------------
1 | #EXTM3U
2 | #EXT-X-DEFINE:QUERYPARAM="user"
3 | #EXT-X-DEFINE:QUERYPARAM="content"
4 |
5 | #EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="stereo",LANGUAGE="en",NAME="English",DEFAULT=YES,AUTOSELECT=YES,URI="audio/stereo/en/128kbit.m3u8"
6 | #EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="stereo",LANGUAGE="dubbing",NAME="Dubbing",DEFAULT=NO,AUTOSELECT=YES,URI="audio/stereo/none/128kbit.m3u8"
7 | #EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="surround",LANGUAGE="dubbing",NAME="Dubbing",DEFAULT=NO,AUTOSELECT=YES,URI="audio/stereo/none/128kbit.m3u8"
8 |
9 | #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=831270,CODECS="avc1.4d4015,mp4a.40.2",AUDIO="stereo",RESOLUTION=638x272
10 | video/800kbit.m3u8?user={$user}&content={$content}
11 |
12 | #EXT-X-ENDLIST
13 |
14 |
15 |
--------------------------------------------------------------------------------
/public/samples/sample-api-vastid-live/index.html:
--------------------------------------------------------------------------------
1 |
2 |
Sample API VASTID (LIVE)
3 |
4 |
5 |
6 |
69 |
70 |
71 |
72 |
86 |
87 |

88 |
Sample API VASTID (LIVE w/BBB)
89 |
90 | HLS, live, 1 ad parsed form
91 | this Google IMA VAST example
92 |
93 |
94 | Source: BBB restreamed as live
95 |
96 |
97 |
98 |
99 |
100 |
API Event: ...
101 |
Current Time:
102 |
Program Date Time: ...
103 |
Interstitial: ...
104 |
105 |
106 |
107 |
108 |
187 |
188 |
--------------------------------------------------------------------------------
/public/samples/sample-api-vastid-live2/index.html:
--------------------------------------------------------------------------------
1 |
2 |
Sample API 1
3 |
4 |
5 |
6 |
69 |
70 |
71 |
72 |
86 |
87 |

88 |
Sample API VASTID (LIVE w/WebRTC)
89 |
90 | HLS, live, 1 ad parsed form
91 | this Google IMA VAST example
92 |
93 |
94 | Source:
95 | WebRTC Publish Page
96 |
97 |
98 |
99 |
100 |
101 |
API Event: ...
102 |
Current Time:
103 |
Program Date Time: ...
104 |
Interstitial: ...
105 |
106 |
107 |
108 |
109 |
110 |
187 |
188 |
--------------------------------------------------------------------------------
/public/samples/sample-api-vastid-live3/index.html:
--------------------------------------------------------------------------------
1 |
2 |
Sample API 1
3 |
4 |
5 |
6 |
69 |
70 |
71 |
72 |
86 |
87 |

88 |
Sample API VASTID (LIVE w/rtsp Camera)
89 |
90 | HLS, live, 1 ad parsed form
91 | this Google IMA VAST example
92 |
93 |
94 | Source: rtsp Camera
95 |
96 |
97 |
98 |
99 |
100 |
API Event: ...
101 |
Current Time:
102 |
Program Date Time: ...
103 |
Interstitial: ...
104 |
105 |
106 |
107 |
108 |
109 |
186 |
187 |
--------------------------------------------------------------------------------
/public/samples/sample-api-vastid-live4/index.html:
--------------------------------------------------------------------------------
1 |
2 |
Sample API 1
3 |
4 |
5 |
6 |
69 |
70 |
71 |
72 |
86 |
87 |

88 |
Sample API VASTID (LIVE w/rtmp)
89 |
90 | HLS, live, 1 ad parsed form
91 | this Google IMA VAST example
92 |
93 |
94 | Source: rtmp
ffmpeg -stats -re -f lavfi -i testsrc=size=1280x720,format=yuv420p -f lavfi -i anullsrc -vf "drawtext=fontsize=120:fontcolor=black:text='%{gmtime\:%T}':x=24:y=24,drawtext=fontsize=120:fontcolor=white:text='%{gmtime\:%T}':x=20:y=20" -c:v libx264 -g 50 -c:a aac -f flv rtmp://demo.entrypoint.cloud.wowza.com/rtmp/mystream
95 |
96 |
97 |
98 |
99 |
100 |
API Event: ...
101 |
Current Time:
102 |
Program Date Time: ...
103 |
Interstitial: ...
104 |
105 |
106 |
107 |
108 |
186 |
187 |
--------------------------------------------------------------------------------
/public/samples/sample-api-vastid/index.html:
--------------------------------------------------------------------------------
1 |
2 |
Sample API 1
3 |
4 |
67 |
68 |
69 |
70 |
84 |
85 |
Sample API VASTID
86 |
87 | HLS, interstitial@5sec, 1 ad parsed form
88 | this Google IMA VAST example
89 |
90 |
91 | API Call:
92 | try out
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
175 |
176 |
--------------------------------------------------------------------------------
/public/samples/sample-api-vastid/main.m3u8:
--------------------------------------------------------------------------------
1 | #EXTM3U
2 | #EXT-X-DEFINE:QUERYPARAM="user"
3 | #EXT-X-DEFINE:QUERYPARAM="content"
4 |
5 | #EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="stereo",LANGUAGE="en",NAME="English",DEFAULT=YES,AUTOSELECT=YES,URI="audio/stereo/en/128kbit.m3u8"
6 | #EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="stereo",LANGUAGE="dubbing",NAME="Dubbing",DEFAULT=NO,AUTOSELECT=YES,URI="audio/stereo/none/128kbit.m3u8"
7 | #EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="surround",LANGUAGE="dubbing",NAME="Dubbing",DEFAULT=NO,AUTOSELECT=YES,URI="audio/stereo/none/128kbit.m3u8"
8 |
9 | #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=831270,CODECS="avc1.4d4015,mp4a.40.2",AUDIO="stereo",RESOLUTION=638x272
10 | video/800kbit.m3u8?user={$user}&content={$content}
11 |
12 | #EXT-X-ENDLIST
13 |
14 |
15 |
--------------------------------------------------------------------------------
/public/samples/sample-api-vasturl/index.html:
--------------------------------------------------------------------------------
1 |
2 |
Sample API 1
3 |
4 |
67 |
68 |
69 |
70 |
84 |
85 |
Sample API VASTURL
86 |
87 | HLS, interstitial@5sec, 1 ad parsed form
88 | this Google IMA VAST example
89 |
90 |
91 | API Call:
92 | try out
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
176 |
177 |
--------------------------------------------------------------------------------
/public/samples/sample-api-vasturl/main.m3u8:
--------------------------------------------------------------------------------
1 | #EXTM3U
2 | #EXT-X-DEFINE:QUERYPARAM="user"
3 | #EXT-X-DEFINE:QUERYPARAM="content"
4 |
5 | #EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="stereo",LANGUAGE="en",NAME="English",DEFAULT=YES,AUTOSELECT=YES,URI="audio/stereo/en/128kbit.m3u8"
6 | #EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="stereo",LANGUAGE="dubbing",NAME="Dubbing",DEFAULT=NO,AUTOSELECT=YES,URI="audio/stereo/none/128kbit.m3u8"
7 | #EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="surround",LANGUAGE="dubbing",NAME="Dubbing",DEFAULT=NO,AUTOSELECT=YES,URI="audio/stereo/none/128kbit.m3u8"
8 |
9 | #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=831270,CODECS="avc1.4d4015,mp4a.40.2",AUDIO="stereo",RESOLUTION=638x272
10 | video/800kbit.m3u8?user={$user}&content={$content}
11 |
12 | #EXT-X-ENDLIST
13 |
14 |
15 |
--------------------------------------------------------------------------------
/public/samples/sample-edit-vast/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
Sample VAST 1
7 |
8 |
9 |
10 |
11 |
149 |
150 |
151 |
159 |
182 |
183 |
184 |
VAST File
185 |
Waiting for VAST file...
186 |
187 |
188 |
189 |
Parsed Asset List
190 |
Waiting for asset-list...
191 |
192 |
193 |
194 |
302 |
303 |
--------------------------------------------------------------------------------
/public/samples/sample-edit-vast/main.m3u8:
--------------------------------------------------------------------------------
1 | #EXTM3U
2 | #EXT-X-DEFINE:QUERYPARAM="duration"
3 | #EXT-X-DEFINE:QUERYPARAM="vasturl"
4 | #EXT-X-DEFINE:QUERYPARAM="startDate"
5 | #EXT-X-DEFINE:QUERYPARAM="resumeOffset"
6 | #EXT-X-DEFINE:QUERYPARAM="restrict"
7 |
8 |
9 | #EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="stereo",LANGUAGE="en",NAME="English",DEFAULT=YES,AUTOSELECT=YES,URI="audio/stereo/en/128kbit.m3u8"
10 | #EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="stereo",LANGUAGE="dubbing",NAME="Dubbing",DEFAULT=NO,AUTOSELECT=YES,URI="audio/stereo/none/128kbit.m3u8"
11 | #EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="surround",LANGUAGE="dubbing",NAME="Dubbing",DEFAULT=NO,AUTOSELECT=YES,URI="audio/stereo/none/128kbit.m3u8"
12 |
13 | #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=831270,CODECS="avc1.4d4015,mp4a.40.2",AUDIO="stereo",RESOLUTION=638x272
14 | video/800kbit.m3u8?duration={$duration}&vasturl={$vasturl}&startDate={$startDate}&resumeOffset={$resumeOffset}&restrict={$restrict}
15 |
16 | #EXT-X-ENDLIST
17 |
18 |
19 |
--------------------------------------------------------------------------------
/public/samples/sample-vast-1/index.html:
--------------------------------------------------------------------------------
1 |
2 |
Sample VAST 1
3 |
4 |
67 |
68 |
69 |
70 |
84 |
95 |
96 |
169 |
170 |
--------------------------------------------------------------------------------
/public/samples/sample-vast-1/main.m3u8:
--------------------------------------------------------------------------------
1 | #EXTM3U
2 | #EXT-X-DEFINE:QUERYPARAM="user"
3 | #EXT-X-DEFINE:QUERYPARAM="content"
4 |
5 | #EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="stereo",LANGUAGE="en",NAME="English",DEFAULT=YES,AUTOSELECT=YES,URI="audio/stereo/en/128kbit.m3u8"
6 | #EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="stereo",LANGUAGE="dubbing",NAME="Dubbing",DEFAULT=NO,AUTOSELECT=YES,URI="audio/stereo/none/128kbit.m3u8"
7 | #EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="surround",LANGUAGE="dubbing",NAME="Dubbing",DEFAULT=NO,AUTOSELECT=YES,URI="audio/stereo/none/128kbit.m3u8"
8 |
9 | #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=831270,CODECS="avc1.4d4015,mp4a.40.2",AUDIO="stereo",RESOLUTION=638x272
10 | video/800kbit.m3u8?user={$user}&content={$content}
11 |
12 | #EXT-X-ENDLIST
13 |
14 |
15 |
--------------------------------------------------------------------------------
/public/samples/sample-vast-set/vast_01_15s.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Sample
6 | External - Single Inline Linear
7 |
8 |
9 |
10 |
11 |
12 |
13 | 00:00:15
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/public/samples/sample-vast-set/vast_02_30s.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Sample
6 | External - Single Inline Linear
7 |
8 |
9 |
10 |
11 |
12 |
13 | 00:00:30
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/public/samples/sample-vast-set/vast_03_30s.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Sample
6 | External - Single Inline Linear
7 |
8 |
9 |
10 |
11 |
12 |
13 | 00:00:30
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/public/samples/sample-vast-set/vast_04_15s.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Sample
6 | External - Single Inline Linear
7 |
8 |
9 |
10 |
11 |
12 |
13 | 00:00:15
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/public/samples/sample-vast-set/vast_05_15s.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Sample
6 | External - Single Inline Linear
7 |
8 |
9 |
10 |
11 |
12 |
13 | 00:00:15
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/public/samples/sample-vast-set/vast_06-7_30s.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Sample
6 | External - Double Inline Linear
7 |
8 |
9 |
10 |
11 |
12 |
13 | 00:00:15
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | Sample
48 | External - Double Inline Linear
49 |
50 |
51 |
52 |
53 |
54 |
55 | 00:00:15
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/public/samples/sample-vast-set/vast_08_15s.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Sample
6 | External - Single Inline Linear
7 |
8 |
9 |
10 |
11 |
12 |
13 | 00:00:15
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/public/samples/sample-vast-set/vast_09_15s.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Sample
6 | External - Single Inline Linear
7 |
8 |
9 |
10 |
11 |
12 |
13 | 00:00:15
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/public/samples/wse_api.js:
--------------------------------------------------------------------------------
1 | function sendGetRequest(app,stream,attempt=1)
2 | {
3 | console.log("Sending ad insert attempt:"+attempt);
4 | $('#response').text("...");
5 | var adObject = new Object();
6 | adObject.id = app + "-ad";
7 | //adObject.start_date = "+10";
8 | adObject.asset_list = "https://vast2sgai.qualabs.com/api/asset-list?vasturl=http://vast2sgai.qualabs.com/samples/sample-api-vastid-live/vast-sample.xml&test=false&user=user&content=content"
9 |
10 | $.post({
11 | url: 'https://demo.entrypoint.cloud.wowza.com/v1/ads/applications/'+app+'/streams/' + stream,
12 | type: 'post',
13 | timeout: 2000,
14 | tryCount : 0,
15 | retryLimit : 3,
16 | dataType: 'json',
17 | contentType: "application/json",
18 | data: JSON.stringify(adObject),
19 | success: function (okdata) {
20 | msg = "OK:"+okdata;
21 | console.log(msg);
22 | $('#response').text(msg.trim());
23 | },
24 | error: function (xhr, ajaxOptions, thrownError) {
25 | if(xhr.status == 0) {
26 | var msg = "insert failed:" + xhr.status + "=>" + xhr.responseText + " (attempt:" + attempt + ")"
27 | $('#response').text(msg.trim());
28 | console.log(msg);
29 | if(attempt < 3) {
30 | console.log("Retyring");
31 | sendGetRequest(app,stream,attempt+1);
32 | } else {
33 | $('#response').text(msg);
34 | }
35 | } else if(xhr.status == 200) {
36 | msg = "insert ok:" + xhr.status + "=>" + xhr.responseText;
37 | console.log(msg);
38 | $('#response').text(msg.trim());
39 | }
40 | else {
41 | msg = "insert failed:" + xhr.status + "=>" + xhr.responseText;
42 | console.log(msg);
43 | $('#response').text(msg.trim());
44 | }
45 | }
46 | });
47 | };
48 |
49 | function sendDeleteRequest(app,stream,attempt=1)
50 | {
51 | console.log("Sending ad remove attempt:"+attempt);
52 | $('#response').text("...");
53 | var adObject = new Object();
54 | $.post({
55 | url: 'https://demo.entrypoint.cloud.wowza.com/v1/ads/applications/'+app+'/streams/' + stream,
56 | type: 'delete',
57 | dataType: 'json',
58 | timeout: 2000,
59 | tryCount : 0,
60 | retryLimit : 3,
61 | contentType: "application/json",
62 | data: JSON.stringify(adObject),
63 | success: function (okdata) {
64 | msg = "Deleted";
65 | console.log(msg);
66 | $('#response').text(msg.trim());
67 | },
68 | error: function (xhr, ajaxOptions, thrownError) {
69 | if(xhr.status == 0) {
70 | msg = "delete failed:" + xhr.status + "=>" + xhr.responseText + " (attempt:" + attempt + ")";
71 | console.log(msg);
72 | if(attempt < 3) {
73 | console.log("trying...");
74 | sendDeleteRequest(app,stream,attempt+1);
75 | } else {
76 | $('#response').text(msg.trim());
77 | }
78 | } else if(xhr.status == 204) {
79 | msg = "delete ok:" + xhr.status + "=>" + xhr.responseText;
80 | console.log(msg);
81 | $('#response').text(msg.trim());
82 | }
83 | else {
84 | msg = "delete failed:" + xhr.status + "=>" + xhr.responseText;
85 | console.log(msg);
86 | $('#response').text(msg.trim());
87 | }
88 | }
89 | });
90 | }
91 |
92 | function checkTime(i) {
93 | // add a zero in front of numbers<10
94 | if (i < 10) {
95 | i = "0" + i;
96 | }
97 | return i;
98 | }
99 |
100 | function startTime() {
101 | var today = new Date();
102 | $('#time').text(checkTime(today.getUTCHours()) + ":" + checkTime(today.getUTCMinutes()) + ":" + checkTime(today.getUTCSeconds()));
103 |
104 | if (hls && hls.playingDate){
105 | var programDateTime = hls.playingDate
106 | $('#program-date-time').text(checkTime(programDateTime.getUTCHours()) + ":" + checkTime(programDateTime.getUTCMinutes()) + ":" + checkTime(programDateTime.getUTCSeconds()));
107 | }
108 | setTimeout(function() {
109 | startTime()
110 | }, 500);
111 | }
--------------------------------------------------------------------------------
/public/wse.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/montevideo-tech/vast-2-sgai/b883cafcdc417a4afac4273281fcdc1880c2f1ef/public/wse.png
--------------------------------------------------------------------------------
/src/config/vast-mapping.js:
--------------------------------------------------------------------------------
1 | const vastMappingEnv = process.env.VAST_MAPPING_JSON || "";
2 |
3 | let vastMapping;
4 | try {
5 | vastMapping = JSON.parse(vastMappingEnv);
6 | } catch {
7 | vastMapping = {};
8 | }
9 |
10 | module.exports = vastMapping;
11 |
--------------------------------------------------------------------------------
/src/middlewares/params.js:
--------------------------------------------------------------------------------
1 | const { middlewareJWTQuery } = require("../utils/jwt");
2 | const { vastServerWhitelistMiddleware } = require("./whitelist");
3 | const { vastIdMiddleware } = require("./vast-id");
4 |
5 | function paramsMiddleware(req, res, next) {
6 | const { jwt, vasturl, vastid } = req.query;
7 |
8 | const params = [jwt, vasturl, vastid].filter((param) => param !== undefined);
9 | if (params.length > 1) {
10 | return res
11 | .status(400)
12 | .json({
13 | message: "Provide only one param at a time: jwt, vasturl or vastid",
14 | });
15 | }
16 |
17 | if (jwt) {
18 | return middlewareJWTQuery(req, res, next);
19 | } else if (vasturl) {
20 | return vastServerWhitelistMiddleware(req, res, next);
21 | } else if (vastid) {
22 | return vastIdMiddleware(req, res, next);
23 | } else {
24 | return res
25 | .status(400)
26 | .json({
27 | message: "No valid query param provided. Use jwt, vasturl or vastid",
28 | });
29 | }
30 | }
31 |
32 | module.exports = { paramsMiddleware };
33 |
--------------------------------------------------------------------------------
/src/middlewares/vast-id.js:
--------------------------------------------------------------------------------
1 | const vastMapping = require("../config/vast-mapping");
2 |
3 | function vastIdMiddleware(req, res, next) {
4 | const { vastid } = req.query;
5 | if (!vastid) {
6 | return res.status(400).json({ message: "Missing vastid query param" });
7 | }
8 |
9 | const vastidurl = vastMapping[vastid];
10 | if (!vastidurl) {
11 | return res
12 | .status(404)
13 | .json({ message: `No VAST URL found for id: ${vastid}` });
14 | }
15 |
16 | req.query.vastidurl = vastidurl;
17 | return next();
18 | }
19 |
20 | module.exports = { vastIdMiddleware };
21 |
--------------------------------------------------------------------------------
/src/middlewares/whitelist.js:
--------------------------------------------------------------------------------
1 | require("dotenv").config({ path: "src/.env" });
2 |
3 | const {
4 | VAST_WHITELIST: vastWhitelist = "",
5 | ORIGIN_WHITELIST: originWhitelist = ""
6 | } = process.env;
7 |
8 | function vastServerWhitelistMiddleware(req, res, next) {
9 | const { vasturl } = req.query;
10 |
11 | if (vastWhitelist === "") {
12 | return next();
13 | }
14 |
15 | if (!vasturl) {
16 | return res.status(400).json({ message: "Missing vasturl query param" });
17 | }
18 |
19 | try {
20 | const hostname = new URL(vasturl).hostname;
21 | const isAllowedVast = vastWhitelist.split(",").includes(hostname);
22 |
23 | if (isAllowedVast) {
24 | return next();
25 | } else {
26 | return res
27 | .status(403)
28 | .json({ message: "Forbidden: VAST URL not allowed" });
29 | }
30 | } catch {
31 | return res.status(400).json({ message: "Bad Request: Invalid VAST URL" });
32 | }
33 | }
34 |
35 | function originWhitelistMiddleware(req, res, next) {
36 | const origin = req.headers.origin;
37 |
38 | if (originWhitelist === "") {
39 | return next();
40 | }
41 |
42 | if (!origin) {
43 | return res
44 | .status(400)
45 | .json({ message: "Bad Request: Origin header is required" });
46 | }
47 |
48 | try {
49 | const hostname = new URL(origin).hostname;
50 | const isAllowedOrigin = originWhitelist.split(",").includes(hostname);
51 |
52 | if (isAllowedOrigin) {
53 | return next();
54 | } else {
55 | return res
56 | .status(403)
57 | .json({ message: "Forbidden: Origin host not allowed" });
58 | }
59 | } catch {
60 | return res
61 | .status(400)
62 | .json({ message: "Bad Request: Invalid Origin header" });
63 | }
64 | }
65 |
66 | module.exports = { originWhitelistMiddleware, vastServerWhitelistMiddleware };
67 |
--------------------------------------------------------------------------------
/src/routes/api.js:
--------------------------------------------------------------------------------
1 | // routes/api.js
2 | require("dotenv").config();
3 | const express = require("express");
4 | const bodyParser = require("body-parser");
5 |
6 | const getVideoManifests = require("../utils/vast-parser.js");
7 | const getListMPD = require("../utils/list-mpd-generator.js");
8 | const { signJWT } = require("../utils/jwt.js");
9 | const updateQueryParams = require("../utils/replace-queryparams.js");
10 |
11 | const { originWhitelistMiddleware } = require("../middlewares/whitelist.js");
12 | const { paramsMiddleware } = require("../middlewares/params.js");
13 |
14 | const { AdCreativeSignalingMapper } = require("../trackingEvents/tracking-events.js");
15 |
16 | const API_DISABLE_SIGN = process.env.API_DISABLE_SIGN == "true";
17 |
18 | const router = express.Router();
19 |
20 | async function getAds(req, manifestType) {
21 | //remove all the reserved params from the query params
22 | // eslint-disable-next-line no-unused-vars
23 | const { jwt, vasturl, vastidurl, vastid, ...queryParams } = req.query;
24 |
25 | let jwturl = "";
26 | if (req.jwtPayload) {
27 | jwturl = req.jwtPayload.url;
28 | }
29 |
30 | //get the url from decoded jwt, plain vasturl or vastid mapping
31 | const url = jwturl || vasturl || vastidurl;
32 |
33 | const finalUrl = updateQueryParams(url, queryParams);
34 | req.log.debug(`initial VAST URL : ${url}`);
35 | req.log.debug(`final VAST URL: ${finalUrl}`);
36 | return await getVideoManifests(finalUrl, manifestType);
37 | }
38 |
39 | function mapAdCreativeSignaling(ad) {
40 | const mapper = new AdCreativeSignalingMapper(ad);
41 | const mappedTrackingEvents = mapper.map();
42 |
43 | return mappedTrackingEvents;
44 | }
45 |
46 | // HLS Asset List
47 | router.get(
48 | "/asset-list",
49 | originWhitelistMiddleware,
50 | paramsMiddleware,
51 | async (req, res) => {
52 | let ads;
53 | try {
54 | ads = await getAds(req, "m3u8");
55 | } catch (error) {
56 | return res.status(400).json({
57 | error: error.message,
58 | });
59 | }
60 | const assetList = { ASSETS: [] };
61 | ads.forEach((ad) => {
62 |
63 | const videoClicksSignaling = {
64 | clickThrough: ad.videoClicks?.clickThrough,
65 | clickTracking: ad.videoClicks?.clickTracking,
66 | customClick: ad.videoClicks?.customClick,
67 | };
68 |
69 | const trackingEvents = mapAdCreativeSignaling(ad);
70 | assetList.ASSETS.push({
71 | URI: ad.fileURL,
72 | DURATION: ad.duration,
73 | "X-AD-CREATIVE-SIGNALING": {
74 | version: 2,
75 | type: "slot",
76 | payload: [
77 | {
78 | type: "linear",
79 | start: 0.0,
80 | duration: ad.duration,
81 | tracking: trackingEvents,
82 | },
83 | ],
84 | },
85 | "X-VAST2SGAI-VIDEOCLICKS": videoClicksSignaling,
86 | });
87 | });
88 |
89 | res.json(assetList);
90 | }
91 | );
92 |
93 | // MPEG-DASH MPD List
94 | router.get(
95 | "/list-mpd",
96 | originWhitelistMiddleware,
97 | paramsMiddleware,
98 | async (req, res) => {
99 | const ads = await getAds(req, "mpd");
100 | res.set("Content-Type", "application/dash+xml");
101 | res.send(getListMPD(ads));
102 | }
103 | );
104 |
105 | // Sign JWT
106 | router.all("/sign", bodyParser.json(), (req, res) => {
107 | if (API_DISABLE_SIGN) res.status(401).json({ error: "API disabled" });
108 | const url = req.body.url || req.query.url;
109 | if (!url)
110 | res.status(400).json({
111 | error:
112 | "You must send the 'url' as a query parameter when using GET or include it in the JSON body when using POST.",
113 | });
114 | res.send(signJWT({ url }));
115 | });
116 |
117 | module.exports = router;
--------------------------------------------------------------------------------
/src/routes/samples.js:
--------------------------------------------------------------------------------
1 | // routes/subroutes.js
2 | const express = require("express");
3 | const VAST = require("@dailymotion/vast-client");
4 | const { logger } = require("../utils/logger.js");
5 |
6 | const router = express.Router();
7 |
8 | // Basic Asset List
9 | router.get("/asset-list-1", (req, res) => {
10 | const assetList = {
11 | ASSETS: [
12 | {
13 | URI: "https://flipfit-cdn.akamaized.net/flip_hls/661f570aab9d840019942b80-473e0b/video_h1.m3u8",
14 | DURATION: 45.0,
15 | },
16 | ],
17 | };
18 | res.log.info("Return sample: asset-list-1");
19 | res.json(assetList);
20 | });
21 |
22 | // Ad POD
23 | router.get("/asset-list-2", (req, res) => {
24 | res.json({
25 | ASSETS: [
26 | {
27 | URI: "https://flipfit-cdn.akamaized.net/flip_hls/661f570aab9d840019942b80-473e0b/video_h1.m3u8",
28 | DURATION: 45.0,
29 | },
30 | {
31 | URI: "https://redirector.gvt1.com/api/manifest/hls_variant/id/f1be9c477e89fd68/itag/0/source/dclk_video_ads/acao/yes/cpn/McGcWKwT0_10xtfw/ctier/L/ei/IuBEZ6rhLtrn1sQP0IXQ8A8/hfr/all/ip/0.0.0.0/keepalive/yes/playlist_type/DVR/requiressl/yes/susc/dvc/xpc/Eghovf3BOnoBAQ%3D%3D/expire/1764103074/sparams/expire,ei,ip,requiressl,acao,ctier,source,playlist_type,hfr,id,itag,susc,xpc/sig/AJfQdSswRAIgc0tQOyv3LSTCNBEv8q_nWaSWwq-EdQ870E7JFyy_dVQCICCKy-TAM88ZpQOHiXKAcgfj1ezoru62WiR_A-1epmla/file/index.m3u8",
32 | DURATION: 10.0,
33 | },
34 | ],
35 | });
36 | });
37 |
38 | router.get("/sample-vast-1", async (req, res) => {
39 | const vastClient = new VAST.VASTClient();
40 | const parsedVast = await vastClient.get(
41 | "http://localhost:3000/samples/sample-vast-1/vast-sample.xml"
42 | );
43 |
44 | logger.info(parsedVast);
45 | logger.info(parsedVast.ads[0]);
46 | logger.info(parsedVast.ads[0].creatives[0]);
47 | logger.info(parsedVast.ads[0].creatives[0].mediaFiles[4]);
48 |
49 | const fileURL = parsedVast.ads[0].creatives[0].mediaFiles[4].fileURL;
50 | const duration = parsedVast.ads[0].creatives[0].duration;
51 | res.json({
52 | ASSETS: [
53 | {
54 | URI: fileURL,
55 | DURATION: duration,
56 | },
57 | ],
58 | });
59 | });
60 |
61 | module.exports = router;
62 |
--------------------------------------------------------------------------------
/src/server.js:
--------------------------------------------------------------------------------
1 | require("dotenv").config();
2 | const express = require("express");
3 | const path = require("path");
4 | const cors = require("cors");
5 |
6 | const samples = require("./routes/samples.js");
7 | const api = require("./routes/api.js");
8 | const { httpLogger, logger } = require("./utils/logger.js");
9 |
10 | const app = express();
11 | const PORT = process.env.PORT || 3000;
12 |
13 | app.use(cors());
14 |
15 | app.use("/api/", httpLogger, api);
16 | app.use("/api/samples/", httpLogger, samples);
17 |
18 | // Static samples
19 | app.use(express.static(path.join(__dirname, "../public")));
20 |
21 | // Start the server
22 | app.listen(PORT, () => {
23 | logger.info(`VAST-2-SGAI is running on http://localhost:${PORT}`);
24 | });
25 |
26 | module.exports = app;
27 |
--------------------------------------------------------------------------------
/src/trackingEvents/tracking-events.js:
--------------------------------------------------------------------------------
1 | const { logger } = require("../utils/logger.js");
2 |
3 | const IMPRESSION = "impression";
4 | const PROGRESS = "progress";
5 |
6 | class TrackingEvent {
7 | constructor(type, duration, urls) {
8 | // console.log(">>>TRACKING EVT: ", type)
9 | this.type = type;
10 | //this.duration = duration;
11 | this.urls = urls;
12 | //this.start = start;
13 |
14 | if (this.type.startsWith(PROGRESS)) {
15 | const offsetValue = this.type.split("-")[1];
16 | if (offsetValue !== null) {
17 | this.offset =
18 | typeof offsetValue === "string" && offsetValue.includes("%")
19 | ? (duration * parseFloat(offsetValue.replace("%", ""))) / 100
20 | : parseFloat(offsetValue);
21 |
22 | this.type = PROGRESS;
23 |
24 | if (
25 | isNaN(this.offset) ||
26 | this.offset < 0 ||
27 | this.offset > this.duration
28 | ) {
29 | throw new Error(`Invalid start value: ${offset}`);
30 | }
31 | }
32 | } else {
33 | switch (type) {
34 | case "start":
35 | this.offset = 0;
36 | break;
37 |
38 | case "firstQuartile":
39 | this.offset = duration * 0.25;
40 | break;
41 |
42 | case "midpoint":
43 | this.offset = duration * 0.5;
44 | break;
45 |
46 | case "thirdQuartile":
47 | this.offset = duration * 0.75;
48 | break;
49 |
50 | case "complete":
51 | this.offset = duration;
52 | break;
53 | }
54 | }
55 | }
56 | }
57 |
58 | class AdCreativeSignalingMapper {
59 | constructor(ad) {
60 | this.ad = ad;
61 | console.log(">>>> Ad Tracking", this.ad.trackingEvents);
62 | }
63 |
64 | map() {
65 | const newTrackingEvents = this.ad.trackingEvents;
66 |
67 | if (this.ad?.impressions.length > 0)
68 | newTrackingEvents[IMPRESSION] = this.ad.impressions.map(
69 | (impression) => impression.url
70 | );
71 |
72 | const trackingEvents = Object.entries(this.ad.trackingEvents)
73 | .map(([eventType, urls]) => {
74 | try {
75 | const event = new TrackingEvent(eventType, this.ad.duration, urls);
76 | //console.log(">>> track evt: ", event)
77 | return event;
78 | } catch (error) {
79 | logger.warn(
80 | `Skipping invalid event: ${eventType}, Error: ${error.message}`
81 | );
82 |
83 | return null;
84 | }
85 | })
86 | .filter(Boolean);
87 |
88 | return trackingEvents;
89 | }
90 | }
91 |
92 | module.exports = {
93 | AdCreativeSignalingMapper,
94 | TrackingEvent,
95 | };
96 |
--------------------------------------------------------------------------------
/src/utils/jwt.js:
--------------------------------------------------------------------------------
1 | require("dotenv").config({ path: "src/.env" });
2 | const jwt = require("jsonwebtoken");
3 |
4 | const SECRET_KEY = process.env.JWT_SECRET_KEY;
5 | const BYPASS_VALIDATION = process.env.JWT_BYPASS_VALIDATION == "true";
6 | const EXPIRES_IN = process.env.JWT_EXPIRES_IN || "100y";
7 |
8 | function signJWT(payload, options = {}) {
9 | const opt = { ...options, expiresIn: EXPIRES_IN };
10 | return jwt.sign(payload, SECRET_KEY, opt);
11 | }
12 |
13 | function getJWT(token) {
14 | if (BYPASS_VALIDATION) return jwt.decode(token);
15 | try {
16 | return jwt.verify(token, SECRET_KEY);
17 | } catch (error) {
18 | throw new Error("Invalid token: " + error);
19 | }
20 | }
21 |
22 | function middlewareJWTHeader(req, res, next) {
23 | const token = req.headers["authorization"]?.split(" ")[1];
24 | if (!token) {
25 | return res.status(401).json({ error: "Token not found" });
26 | }
27 | try {
28 | req.jwtPayload = getJWT(token);
29 | next();
30 | } catch {
31 | return res.status(403).json({ error: "Invalid token" });
32 | }
33 | }
34 |
35 | function middlewareJWTQuery(req, res, next) {
36 | const token = req.query.jwt;
37 |
38 | if (!token)
39 | return res.status(401).json({ error: "jwt query parameter not found" });
40 | try {
41 | req.jwtPayload = getJWT(token);
42 | } catch {
43 | return res.status(403).json({ error: "Invalid token" });
44 | }
45 |
46 | if (!req.jwtPayload.url)
47 | return res.status(401).json({ error: "url not found in jwt" });
48 |
49 | next();
50 | }
51 |
52 | module.exports = { signJWT, getJWT, middlewareJWTHeader, middlewareJWTQuery };
53 |
--------------------------------------------------------------------------------
/src/utils/list-mpd-generator.js:
--------------------------------------------------------------------------------
1 |
2 | const DASH_MIN_BUFFER_TIME = "PT1S";
3 | const DASH_RESOLUTION_TIME_OFFSET = "10";
4 |
5 | function getListMPD(ads){
6 | const publishTime = new Date().toISOString(); // Current publish time
7 |
8 | // Initialize MPD XML structure
9 | let mpdXml = `
10 |
\n`;
16 |
17 | // Generate Period and ImportedMPD elements
18 | ads.forEach((ad, index) => {
19 | const earliestResolutionTimeOffset = index === 0 ? 0 : DASH_RESOLUTION_TIME_OFFSET;
20 | mpdXml += `
21 |
22 | \n`;
23 | });
24 |
25 | // Close MPD element
26 | mpdXml += "";
27 | return mpdXml;
28 | };
29 |
30 | module.exports = getListMPD;
--------------------------------------------------------------------------------
/src/utils/logger.js:
--------------------------------------------------------------------------------
1 | require("dotenv").config();
2 | const pino = require("pino");
3 | const pinoHttp = require("pino-http");
4 |
5 | const logger = pino({
6 | level: process.env.PINO_LOG_LEVEL || "info",
7 | ...(process.env.PINO_LOG_LEVEL === "debug" ? {
8 | transport: {
9 | target: "pino-pretty",
10 | },
11 | } : {})
12 | });
13 |
14 | const httpLogger = pinoHttp({
15 | logger,
16 | serializers: {
17 | req: pino.stdSerializers.wrapRequestSerializer((req) => {
18 | return {
19 | id: req.raw.id,
20 | method: req.raw.method,
21 | path: req.raw.url.split("?")[0], // Remove query params which might be sensitive
22 | // Allowlist useful headers
23 | headers: {
24 | host: req.raw.headers.host,
25 | "user-agent": req.raw.headers["user-agent"],
26 | referer: req.raw.headers.referer,
27 | },
28 | };
29 | }),
30 | res: pino.stdSerializers.wrapResponseSerializer((res) => {
31 | return {
32 | statusCode: res.raw.statusCode,
33 | // Allowlist useful headers
34 | headers: {
35 | "content-type": res.raw.headers["content-type"],
36 | "content-length": res.raw.headers["content-length"],
37 | },
38 | };
39 | }),
40 | },
41 | });
42 |
43 | module.exports = { httpLogger, logger };
44 |
--------------------------------------------------------------------------------
/src/utils/replace-queryparams.js:
--------------------------------------------------------------------------------
1 | function updateQueryParams(url, params) {
2 | const urlObj = new URL(url);
3 | // Update or add the params
4 | for (const [key, value] of Object.entries(params)) {
5 | urlObj.searchParams.set(key, value);
6 | }
7 |
8 | // Update or add the params
9 | for (const [key, value] of Object.entries(params)) {
10 | urlObj.searchParams.set(key, value);
11 | }
12 |
13 | return urlObj.toString();
14 | }
15 |
16 | module.exports = updateQueryParams;
17 |
--------------------------------------------------------------------------------
/src/utils/vast-parser.js:
--------------------------------------------------------------------------------
1 | const VAST = require("@dailymotion/vast-client");
2 | const { logger } = require("./logger.js");
3 | /**
4 | * Fetches video manifests from a VAST URL.
5 | *
6 | * @param {string} vastUrl - The URL of the VAST XML. It should be a valid URL string.
7 | * @param {string} manifestType - The type of manifest to filter. It can be either '.mpd' or '.m3u8'.
8 | * @returns {Promise
} A promise that resolves to an array of objects containing file URLs and durations.
9 | */
10 | async function getVideoManifests(vastUrl, manifestType) {
11 | const vastClient = new VAST.VASTClient();
12 | let parsedVast;
13 | try {
14 | parsedVast = await vastClient.get(vastUrl, { resolveAll: true });
15 | } catch (error) {
16 | logger.error(
17 | "Failed to fetch VAST XML",
18 | error
19 | );
20 | throw new Error(error.message);
21 | }
22 |
23 | const result = [];
24 |
25 | if (parsedVast.ads) {
26 | for (let i = 0; i < parsedVast.ads.length; i++) {
27 | const ad = parsedVast.ads[i];
28 | const { impressionURLTemplates } = ad;
29 | if (ad.creatives) {
30 | for (let j = 0; j < ad.creatives.length; j++) {
31 | const creative = ad.creatives[j];
32 | const duration = creative.duration;
33 | if (creative.mediaFiles) {
34 | for (let k = 0; k < creative.mediaFiles.length; k++) {
35 | const mediaFile = creative.mediaFiles[k];
36 | if (
37 | mediaFile.fileURL &&
38 | mediaFile.fileURL.includes(manifestType)
39 | ) {
40 | result.push({
41 | fileURL: mediaFile.fileURL,
42 | duration,
43 | trackingEvents: creative.trackingEvents,
44 | videoClicks: {
45 | clickThrough: creative.videoClickThroughURLTemplate,
46 | clickTracking: creative.videoClickTrackingURLTemplates,
47 | customClick: creative.videoCustomClickURLTemplates
48 | },
49 | impressions: impressionURLTemplates,
50 | });
51 | }
52 | }
53 | }
54 |
55 | }
56 | }
57 | }
58 | }
59 |
60 | return result;
61 | }
62 |
63 | module.exports = getVideoManifests;
64 |
--------------------------------------------------------------------------------