unified-video-framework 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/.github/workflows/ci.yml +253 -0
  2. package/ANDROID_TV_IMPLEMENTATION.md +313 -0
  3. package/COMPLETION_STATUS.md +165 -0
  4. package/CONTRIBUTING.md +376 -0
  5. package/FINAL_STATUS_REPORT.md +170 -0
  6. package/FRAMEWORK_REVIEW.md +247 -0
  7. package/IMPROVEMENTS_SUMMARY.md +168 -0
  8. package/LICENSE +21 -0
  9. package/NATIVE_APP_INTEGRATION_GUIDE.md +903 -0
  10. package/PAYWALL_RENTAL_FLOW.md +499 -0
  11. package/PLATFORM_SETUP_GUIDE.md +1636 -0
  12. package/README.md +315 -0
  13. package/RUN_LOCALLY.md +151 -0
  14. package/apps/demo/cast-sender-min.html +173 -0
  15. package/apps/demo/custom-player.html +883 -0
  16. package/apps/demo/demo.html +990 -0
  17. package/apps/demo/enhanced-player.html +3556 -0
  18. package/apps/demo/index.html +159 -0
  19. package/apps/rental-api/.env.example +24 -0
  20. package/apps/rental-api/README.md +23 -0
  21. package/apps/rental-api/migrations/001_init.sql +35 -0
  22. package/apps/rental-api/migrations/002_videos.sql +10 -0
  23. package/apps/rental-api/migrations/003_add_gateway_subref.sql +4 -0
  24. package/apps/rental-api/migrations/004_update_gateways.sql +4 -0
  25. package/apps/rental-api/migrations/005_seed_demo_video.sql +5 -0
  26. package/apps/rental-api/package-lock.json +2045 -0
  27. package/apps/rental-api/package.json +33 -0
  28. package/apps/rental-api/scripts/run-migration.js +42 -0
  29. package/apps/rental-api/scripts/update-video-currency.js +21 -0
  30. package/apps/rental-api/scripts/update-video-price.js +19 -0
  31. package/apps/rental-api/src/config.ts +14 -0
  32. package/apps/rental-api/src/db.ts +10 -0
  33. package/apps/rental-api/src/routes/cashfree.ts +167 -0
  34. package/apps/rental-api/src/routes/pesapal.ts +92 -0
  35. package/apps/rental-api/src/routes/rentals.ts +242 -0
  36. package/apps/rental-api/src/routes/webhooks.ts +73 -0
  37. package/apps/rental-api/src/server.ts +41 -0
  38. package/apps/rental-api/src/services/entitlements.ts +45 -0
  39. package/apps/rental-api/src/services/payments.ts +22 -0
  40. package/apps/rental-api/tsconfig.json +17 -0
  41. package/check-urls.ps1 +74 -0
  42. package/comparison-report.md +181 -0
  43. package/docs/PAYWALL.md +95 -0
  44. package/docs/PLAYER_UI_VISIBILITY.md +431 -0
  45. package/docs/README.md +7 -0
  46. package/docs/SYSTEM_ARCHITECTURE.md +612 -0
  47. package/docs/VDOCIPHER_CLONE_REQUIREMENTS.md +403 -0
  48. package/examples/android/JavaSampleApp/MainActivity.java +641 -0
  49. package/examples/android/JavaSampleApp/activity_main.xml +226 -0
  50. package/examples/android/SampleApp/MainActivity.kt +430 -0
  51. package/examples/ios/SampleApp/ViewController.swift +337 -0
  52. package/examples/ios/SwiftUISampleApp/ContentView.swift +304 -0
  53. package/iOS_IMPLEMENTATION_OPTIONS.md +470 -0
  54. package/ios/UnifiedVideoPlayer/UnifiedVideoPlayer.podspec +33 -0
  55. package/jest.config.js +33 -0
  56. package/jitpack.yml +5 -0
  57. package/lerna.json +35 -0
  58. package/package.json +69 -0
  59. package/packages/PLATFORM_STATUS.md +163 -0
  60. package/packages/android/build.gradle +135 -0
  61. package/packages/android/src/main/AndroidManifest.xml +36 -0
  62. package/packages/android/src/main/java/com/unifiedvideo/player/PlayerConfiguration.java +221 -0
  63. package/packages/android/src/main/java/com/unifiedvideo/player/UnifiedVideoPlayer.java +1037 -0
  64. package/packages/android/src/main/java/com/unifiedvideo/player/UnifiedVideoPlayer.kt +707 -0
  65. package/packages/android/src/main/java/com/unifiedvideo/player/analytics/AnalyticsProvider.java +9 -0
  66. package/packages/android/src/main/java/com/unifiedvideo/player/cast/CastManager.java +141 -0
  67. package/packages/android/src/main/java/com/unifiedvideo/player/cast/CastOptionsProvider.java +29 -0
  68. package/packages/android/src/main/java/com/unifiedvideo/player/overlay/WatermarkOverlayView.java +88 -0
  69. package/packages/android/src/main/java/com/unifiedvideo/player/pip/PipActionReceiver.java +33 -0
  70. package/packages/android/src/main/java/com/unifiedvideo/player/services/PlaybackService.java +110 -0
  71. package/packages/android/src/main/java/com/unifiedvideo/player/services/PlayerHolder.java +19 -0
  72. package/packages/core/package.json +34 -0
  73. package/packages/core/src/BasePlayer.ts +250 -0
  74. package/packages/core/src/VideoPlayer.ts +237 -0
  75. package/packages/core/src/VideoPlayerFactory.ts +145 -0
  76. package/packages/core/src/index.ts +20 -0
  77. package/packages/core/src/interfaces/IVideoPlayer.ts +184 -0
  78. package/packages/core/src/interfaces.ts +240 -0
  79. package/packages/core/src/utils/EventEmitter.ts +66 -0
  80. package/packages/core/src/utils/PlatformDetector.ts +300 -0
  81. package/packages/core/tsconfig.json +20 -0
  82. package/packages/enact/package.json +51 -0
  83. package/packages/enact/src/VideoPlayer.js +365 -0
  84. package/packages/enact/src/adapters/TizenAdapter.js +354 -0
  85. package/packages/enact/src/index.js +82 -0
  86. package/packages/ios/BUILD_INSTRUCTIONS.md +108 -0
  87. package/packages/ios/FIX_EMBED_ISSUE.md +142 -0
  88. package/packages/ios/GETTING_STARTED.md +100 -0
  89. package/packages/ios/Package.swift +35 -0
  90. package/packages/ios/README.md +84 -0
  91. package/packages/ios/Sources/UnifiedVideoPlayer/Analytics/AnalyticsEmitter.swift +26 -0
  92. package/packages/ios/Sources/UnifiedVideoPlayer/DRM/FairPlayDRMManager.swift +102 -0
  93. package/packages/ios/Sources/UnifiedVideoPlayer/Info.plist +24 -0
  94. package/packages/ios/Sources/UnifiedVideoPlayer/Remote/RemoteCommandCenter.swift +109 -0
  95. package/packages/ios/Sources/UnifiedVideoPlayer/UnifiedVideoPlayer.swift +811 -0
  96. package/packages/ios/Sources/UnifiedVideoPlayer/UnifiedVideoPlayerView.swift +640 -0
  97. package/packages/ios/Sources/UnifiedVideoPlayer/Utilities/Color+Hex.swift +36 -0
  98. package/packages/ios/UnifiedVideoPlayer.podspec +27 -0
  99. package/packages/ios/UnifiedVideoPlayer.xcodeproj/project.pbxproj +385 -0
  100. package/packages/ios/build_framework.sh +55 -0
  101. package/packages/react-native/android/src/main/java/com/unifiedvideo/UnifiedVideoPlayerModule.kt +482 -0
  102. package/packages/react-native/ios/UnifiedVideoPlayer.swift +436 -0
  103. package/packages/react-native/package.json +51 -0
  104. package/packages/react-native/src/ReactNativePlayer.tsx +423 -0
  105. package/packages/react-native/src/VideoPlayer.tsx +224 -0
  106. package/packages/react-native/src/index.ts +28 -0
  107. package/packages/react-native/src/utils/EventEmitter.ts +66 -0
  108. package/packages/react-native/tsconfig.json +31 -0
  109. package/packages/roku/components/UnifiedVideoPlayer.brs +400 -0
  110. package/packages/roku/package.json +44 -0
  111. package/packages/roku/source/VideoPlayer.brs +231 -0
  112. package/packages/roku/source/main.brs +28 -0
  113. package/packages/web/GETTING_STARTED.md +292 -0
  114. package/packages/web/jest.config.js +28 -0
  115. package/packages/web/jest.setup.ts +110 -0
  116. package/packages/web/package.json +50 -0
  117. package/packages/web/src/SecureVideoPlayer.ts +1164 -0
  118. package/packages/web/src/WebPlayer.ts +3110 -0
  119. package/packages/web/src/__tests__/WebPlayer.test.ts +314 -0
  120. package/packages/web/src/index.ts +14 -0
  121. package/packages/web/src/paywall/PaywallController.ts +215 -0
  122. package/packages/web/src/react/WebPlayerView.tsx +177 -0
  123. package/packages/web/tsconfig.json +23 -0
  124. package/packages/web/webpack.config.js +45 -0
  125. package/server.js +131 -0
  126. package/server.py +84 -0
  127. package/test-urls.ps1 +97 -0
  128. package/test-video-urls.ps1 +87 -0
  129. package/tsconfig.json +39 -0
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Simple event emitter for handling player events in React Native
3
+ */
4
+
5
+ type EventHandler = (...args: any[]) => void;
6
+
7
+ export class EventEmitter {
8
+ private events: Map<string, Set<EventHandler>>;
9
+
10
+ constructor() {
11
+ this.events = new Map();
12
+ }
13
+
14
+ on(event: string, handler: EventHandler): void {
15
+ if (!this.events.has(event)) {
16
+ this.events.set(event, new Set());
17
+ }
18
+ this.events.get(event)!.add(handler);
19
+ }
20
+
21
+ off(event: string, handler?: EventHandler): void {
22
+ if (!this.events.has(event)) return;
23
+
24
+ if (handler) {
25
+ this.events.get(event)!.delete(handler);
26
+ } else {
27
+ this.events.delete(event);
28
+ }
29
+ }
30
+
31
+ once(event: string, handler: EventHandler): void {
32
+ const onceWrapper = (...args: any[]) => {
33
+ handler(...args);
34
+ this.off(event, onceWrapper);
35
+ };
36
+ this.on(event, onceWrapper);
37
+ }
38
+
39
+ emit(event: string, ...args: any[]): void {
40
+ if (!this.events.has(event)) return;
41
+
42
+ this.events.get(event)!.forEach(handler => {
43
+ try {
44
+ handler(...args);
45
+ } catch (error) {
46
+ console.error(`Error in event handler for ${event}:`, error);
47
+ }
48
+ });
49
+ }
50
+
51
+ removeAllListeners(event?: string): void {
52
+ if (event) {
53
+ this.events.delete(event);
54
+ } else {
55
+ this.events.clear();
56
+ }
57
+ }
58
+
59
+ listenerCount(event: string): number {
60
+ return this.events.has(event) ? this.events.get(event)!.size : 0;
61
+ }
62
+
63
+ eventNames(): string[] {
64
+ return Array.from(this.events.keys());
65
+ }
66
+ }
@@ -0,0 +1,31 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "rootDir": "./src",
5
+ "outDir": "./dist",
6
+ "composite": true,
7
+ "jsx": "react-native",
8
+ "lib": ["ES2020"],
9
+ "moduleResolution": "node",
10
+ "allowSyntheticDefaultImports": true,
11
+ "esModuleInterop": true,
12
+ "skipLibCheck": true,
13
+ "resolveJsonModule": true
14
+ },
15
+ "include": [
16
+ "src/**/*"
17
+ ],
18
+ "exclude": [
19
+ "node_modules",
20
+ "dist",
21
+ "**/*.test.ts",
22
+ "**/*.test.tsx",
23
+ "**/*.spec.ts",
24
+ "**/*.spec.tsx",
25
+ "ios",
26
+ "android"
27
+ ],
28
+ "references": [
29
+ { "path": "../core" }
30
+ ]
31
+ }
@@ -0,0 +1,400 @@
1
+ ' UnifiedVideoPlayer Component for Roku
2
+ ' Implements the core video player functionality
3
+
4
+ sub init()
5
+ m.video = m.top.findNode("videoPlayer")
6
+ m.loadingIndicator = m.top.findNode("loadingIndicator")
7
+ m.errorDialog = m.top.findNode("errorDialog")
8
+
9
+ ' Initialize player state
10
+ m.state = {
11
+ isPlaying: false,
12
+ isPaused: true,
13
+ isBuffering: false,
14
+ isEnded: false,
15
+ isError: false,
16
+ currentTime: 0,
17
+ duration: 0,
18
+ bufferedPercentage: 0,
19
+ volume: 100,
20
+ isMuted: false,
21
+ playbackRate: 1.0,
22
+ currentQualityIndex: -1,
23
+ availableQualities: []
24
+ }
25
+
26
+ ' Setup video event observers
27
+ setupVideoObservers()
28
+
29
+ ' Initialize analytics
30
+ m.analytics = CreateObject("roSGNode", "TrackerTask")
31
+ end sub
32
+
33
+ sub setupVideoObservers()
34
+ ' Video player state observers
35
+ m.video.observeField("state", "onVideoStateChange")
36
+ m.video.observeField("position", "onPositionChange")
37
+ m.video.observeField("duration", "onDurationChange")
38
+ m.video.observeField("bufferingStatus", "onBufferingChange")
39
+ m.video.observeField("errorMsg", "onVideoError")
40
+ m.video.observeField("streamInfo", "onStreamInfoChange")
41
+ m.video.observeField("availableAudioTracks", "onAudioTracksChange")
42
+ m.video.observeField("availableSubtitleTracks", "onSubtitleTracksChange")
43
+
44
+ ' Control observers
45
+ m.top.observeField("control", "onControlChange")
46
+ m.top.observeField("seek", "onSeekChange")
47
+ m.top.observeField("volume", "onVolumeChange")
48
+ end sub
49
+
50
+ ' Load video content
51
+ sub loadContent(contentNode as Object)
52
+ if contentNode = invalid then
53
+ showError("Invalid content")
54
+ return
55
+ end if
56
+
57
+ ' Create content node if string URL provided
58
+ if type(contentNode) = "roString" then
59
+ content = CreateObject("roSGNode", "ContentNode")
60
+ content.url = contentNode
61
+ content.streamFormat = detectStreamFormat(contentNode)
62
+ else
63
+ content = contentNode
64
+ end if
65
+
66
+ ' Handle DRM if present
67
+ if content.drmParams <> invalid then
68
+ configureDRM(content)
69
+ end if
70
+
71
+ ' Handle subtitles
72
+ if content.subtitleConfig <> invalid then
73
+ configureSubtitles(content)
74
+ end if
75
+
76
+ ' Set content to video player
77
+ m.video.content = content
78
+
79
+ ' Update state
80
+ m.top.state = "loading"
81
+ m.loadingIndicator.visible = true
82
+
83
+ ' Log analytics event
84
+ trackEvent("video_load", {url: content.url})
85
+ end sub
86
+
87
+ ' Detect stream format from URL
88
+ function detectStreamFormat(url as String) as String
89
+ if instr(url, ".m3u8") > 0 then
90
+ return "hls"
91
+ else if instr(url, ".mpd") > 0 then
92
+ return "dash"
93
+ else if instr(url, ".ism") > 0 then
94
+ return "ism"
95
+ else
96
+ return "mp4"
97
+ end if
98
+ end function
99
+
100
+ ' Configure DRM
101
+ sub configureDRM(content as Object)
102
+ drmParams = content.drmParams
103
+
104
+ if drmParams.type = "widevine" then
105
+ ' Widevine configuration
106
+ content.drmLicenseUrl = drmParams.licenseUrl
107
+ content.drmKeySystem = "widevine"
108
+
109
+ if drmParams.headers <> invalid then
110
+ content.httpHeaders = drmParams.headers
111
+ end if
112
+ else if drmParams.type = "playready" then
113
+ ' PlayReady configuration
114
+ content.drmLicenseUrl = drmParams.licenseUrl
115
+ content.drmKeySystem = "playready"
116
+ end if
117
+ end sub
118
+
119
+ ' Configure subtitles
120
+ sub configureSubtitles(content as Object)
121
+ subtitleConfig = content.subtitleConfig
122
+
123
+ if subtitleConfig <> invalid and subtitleConfig.Count() > 0 then
124
+ subtitleTracks = []
125
+
126
+ for each subtitle in subtitleConfig
127
+ track = {
128
+ url: subtitle.url,
129
+ language: subtitle.language,
130
+ name: subtitle.label
131
+ }
132
+ subtitleTracks.push(track)
133
+ end for
134
+
135
+ content.subtitleTracks = subtitleTracks
136
+ end if
137
+ end sub
138
+
139
+ ' Handle video state changes
140
+ sub onVideoStateChange()
141
+ state = m.video.state
142
+
143
+ if state = "playing" then
144
+ m.state.isPlaying = true
145
+ m.state.isPaused = false
146
+ m.loadingIndicator.visible = false
147
+ m.top.state = "playing"
148
+ trackEvent("video_play")
149
+
150
+ else if state = "paused" then
151
+ m.state.isPlaying = false
152
+ m.state.isPaused = true
153
+ m.top.state = "paused"
154
+ trackEvent("video_pause")
155
+
156
+ else if state = "buffering" then
157
+ m.state.isBuffering = true
158
+ m.loadingIndicator.visible = true
159
+ m.top.state = "buffering"
160
+
161
+ else if state = "finished" then
162
+ m.state.isEnded = true
163
+ m.state.isPlaying = false
164
+ m.top.state = "ended"
165
+ trackEvent("video_complete")
166
+
167
+ else if state = "error" then
168
+ m.state.isError = true
169
+ m.state.isPlaying = false
170
+ handleError()
171
+ end if
172
+ end sub
173
+
174
+ ' Handle position changes
175
+ sub onPositionChange()
176
+ m.state.currentTime = m.video.position
177
+ m.top.currentTime = m.video.position
178
+
179
+ ' Calculate buffered percentage
180
+ if m.video.duration > 0 then
181
+ m.state.bufferedPercentage = (m.video.bufferingStatus.percentage / 100.0)
182
+ m.top.bufferedPercentage = m.state.bufferedPercentage
183
+ end if
184
+ end sub
185
+
186
+ ' Handle duration change
187
+ sub onDurationChange()
188
+ m.state.duration = m.video.duration
189
+ m.top.duration = m.video.duration
190
+ end sub
191
+
192
+ ' Handle buffering changes
193
+ sub onBufferingChange()
194
+ bufferingStatus = m.video.bufferingStatus
195
+
196
+ if bufferingStatus <> invalid then
197
+ m.state.isBuffering = bufferingStatus.percentage < 100
198
+ m.top.isBuffering = m.state.isBuffering
199
+
200
+ if m.state.isBuffering then
201
+ m.loadingIndicator.visible = true
202
+ else
203
+ m.loadingIndicator.visible = false
204
+ end if
205
+ end if
206
+ end sub
207
+
208
+ ' Handle stream info changes (for quality detection)
209
+ sub onStreamInfoChange()
210
+ streamInfo = m.video.streamInfo
211
+
212
+ if streamInfo <> invalid then
213
+ ' Extract quality information
214
+ qualities = []
215
+
216
+ if streamInfo.streamBitrate <> invalid then
217
+ ' For HLS/DASH adaptive streaming
218
+ for each variant in streamInfo.variants
219
+ quality = {
220
+ height: variant.height,
221
+ width: variant.width,
222
+ bitrate: variant.bitrate,
223
+ label: variant.height.ToStr() + "p",
224
+ index: qualities.Count()
225
+ }
226
+ qualities.push(quality)
227
+ end for
228
+ else
229
+ ' Single quality stream
230
+ quality = {
231
+ height: streamInfo.videoHeight,
232
+ width: streamInfo.videoWidth,
233
+ bitrate: streamInfo.measuredBitrate,
234
+ label: streamInfo.videoHeight.ToStr() + "p",
235
+ index: 0
236
+ }
237
+ qualities.push(quality)
238
+ end if
239
+
240
+ m.state.availableQualities = qualities
241
+ m.top.availableQualities = qualities
242
+ end if
243
+ end sub
244
+
245
+ ' Playback control methods
246
+ sub play()
247
+ m.video.control = "play"
248
+ m.state.isPlaying = true
249
+ m.state.isPaused = false
250
+ end sub
251
+
252
+ sub pause()
253
+ m.video.control = "pause"
254
+ m.state.isPlaying = false
255
+ m.state.isPaused = true
256
+ end sub
257
+
258
+ sub stop()
259
+ m.video.control = "stop"
260
+ m.state.isPlaying = false
261
+ m.state.isPaused = true
262
+ m.state.currentTime = 0
263
+ end sub
264
+
265
+ sub seek(position as Float)
266
+ m.video.seek = position
267
+ trackEvent("video_seek", {position: position})
268
+ end sub
269
+
270
+ ' Volume control
271
+ sub setVolume(level as Float)
272
+ ' Roku doesn't support direct volume control
273
+ ' This would typically control the audio track volume
274
+ m.state.volume = level * 100
275
+ m.top.volume = m.state.volume
276
+ end sub
277
+
278
+ sub mute()
279
+ m.video.mute = true
280
+ m.state.isMuted = true
281
+ end sub
282
+
283
+ sub unmute()
284
+ m.video.mute = false
285
+ m.state.isMuted = false
286
+ end sub
287
+
288
+ ' Quality selection
289
+ sub setQuality(index as Integer)
290
+ if index >= 0 and index < m.state.availableQualities.Count() then
291
+ ' For adaptive streaming, this would set max bitrate
292
+ quality = m.state.availableQualities[index]
293
+
294
+ ' Set preferred bitrate for adaptive streaming
295
+ m.video.maxVideoBitrate = quality.bitrate
296
+ m.state.currentQualityIndex = index
297
+
298
+ trackEvent("quality_change", {
299
+ height: quality.height,
300
+ bitrate: quality.bitrate
301
+ })
302
+ end if
303
+ end sub
304
+
305
+ ' Enable/disable adaptive bitrate
306
+ sub setAdaptiveBitrate(enabled as Boolean)
307
+ if enabled then
308
+ m.video.enableAdaptiveBitrate = true
309
+ m.video.maxVideoBitrate = 0 ' No limit
310
+ else
311
+ m.video.enableAdaptiveBitrate = false
312
+ end if
313
+ end sub
314
+
315
+ ' Subtitle control
316
+ sub setSubtitleTrack(index as Integer)
317
+ if m.video.availableSubtitleTracks <> invalid then
318
+ if index >= 0 and index < m.video.availableSubtitleTracks.Count() then
319
+ m.video.subtitleTrack = index
320
+ end if
321
+ end if
322
+ end sub
323
+
324
+ sub disableSubtitles()
325
+ m.video.subtitleTrack = -1
326
+ end sub
327
+
328
+ ' Audio track selection
329
+ sub setAudioTrack(index as Integer)
330
+ if m.video.availableAudioTracks <> invalid then
331
+ if index >= 0 and index < m.video.availableAudioTracks.Count() then
332
+ m.video.audioTrack = index
333
+ end if
334
+ end if
335
+ end sub
336
+
337
+ ' Error handling
338
+ sub onVideoError()
339
+ errorMsg = m.video.errorMsg
340
+ errorCode = m.video.errorCode
341
+
342
+ m.state.isError = true
343
+ m.state.isPlaying = false
344
+ m.loadingIndicator.visible = false
345
+
346
+ showError(errorMsg, errorCode)
347
+
348
+ trackEvent("video_error", {
349
+ code: errorCode,
350
+ message: errorMsg
351
+ })
352
+ end sub
353
+
354
+ sub handleError()
355
+ errorInfo = m.video.errorInfo
356
+ if errorInfo <> invalid then
357
+ showError(errorInfo.message, errorInfo.code)
358
+ else
359
+ showError("An unknown error occurred")
360
+ end if
361
+ end sub
362
+
363
+ sub showError(message as String, code = 0 as Dynamic)
364
+ m.errorDialog.title = "Playback Error"
365
+ m.errorDialog.message = message
366
+ if code <> invalid then
367
+ m.errorDialog.message = m.errorDialog.message + " (Code: " + code.ToStr() + ")"
368
+ end if
369
+ m.errorDialog.visible = true
370
+ end sub
371
+
372
+ ' Analytics tracking
373
+ sub trackEvent(eventName as String, params = {} as Object)
374
+ if m.analytics <> invalid then
375
+ m.analytics.trackEvent = {
376
+ event: eventName,
377
+ params: params,
378
+ timestamp: CreateObject("roDateTime").AsSeconds()
379
+ }
380
+ end if
381
+ end sub
382
+
383
+ ' Get current player state
384
+ function getState() as Object
385
+ return m.state
386
+ end function
387
+
388
+ ' Clean up
389
+ sub destroy()
390
+ m.video.control = "stop"
391
+ m.video.content = invalid
392
+
393
+ ' Remove observers
394
+ m.video.unobserveField("state")
395
+ m.video.unobserveField("position")
396
+ m.video.unobserveField("duration")
397
+ m.video.unobserveField("bufferingStatus")
398
+ m.video.unobserveField("errorMsg")
399
+ m.video.unobserveField("streamInfo")
400
+ end sub
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@unified-video/roku",
3
+ "version": "1.0.0",
4
+ "description": "Roku implementation of Unified Video Framework",
5
+ "main": "source/main.brs",
6
+ "files": [
7
+ "source",
8
+ "components",
9
+ "images",
10
+ "manifest"
11
+ ],
12
+ "scripts": {
13
+ "build": "node scripts/build.js",
14
+ "deploy": "node scripts/deploy.js",
15
+ "package": "zip -r dist/roku-app.zip manifest source components images",
16
+ "clean": "rm -rf dist out",
17
+ "lint": "bslint source/**/*.brs components/**/*.xml",
18
+ "test": "echo 'Roku testing requires device or emulator'"
19
+ },
20
+ "devDependencies": {
21
+ "@rokucommunity/bslint": "^0.8.0",
22
+ "brighterscript": "^0.60.0",
23
+ "roku-deploy": "^3.0.0"
24
+ },
25
+ "keywords": [
26
+ "video",
27
+ "player",
28
+ "roku",
29
+ "brightscript",
30
+ "streaming",
31
+ "ott"
32
+ ],
33
+ "roku": {
34
+ "buildDir": "out",
35
+ "sourceDir": "source",
36
+ "componentsDir": "components",
37
+ "imagesDir": "images"
38
+ },
39
+ "author": "Your Company",
40
+ "license": "MIT",
41
+ "publishConfig": {
42
+ "access": "public"
43
+ }
44
+ }