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,141 @@
1
+ package com.unifiedvideo.player.cast;
2
+
3
+ import android.net.Uri;
4
+ import android.util.Log;
5
+
6
+ import com.google.android.gms.cast.MediaInfo;
7
+ import com.google.android.gms.cast.MediaLoadRequestData;
8
+ import com.google.android.gms.cast.MediaMetadata;
9
+ import com.google.android.gms.cast.framework.CastContext;
10
+ import com.google.android.gms.cast.framework.CastSession;
11
+ import com.google.android.gms.cast.framework.SessionManagerListener;
12
+ import com.google.android.gms.cast.framework.media.RemoteMediaClient;
13
+
14
+ import java.util.ArrayList;
15
+ import java.util.List;
16
+
17
+ /**
18
+ * Minimal Cast manager to load media on a remote receiver.
19
+ * Host app is expected to provide a Cast button via CastButtonFactory.
20
+ */
21
+ public class CastManager {
22
+ private static final String TAG = "UVF-CastManager";
23
+
24
+ private final CastContext castContext;
25
+
26
+ public CastManager() {
27
+ this.castContext = CastContext.getSharedInstance();
28
+ }
29
+
30
+ public boolean hasSession() {
31
+ CastSession session = castContext.getSessionManager().getCurrentCastSession();
32
+ return session != null && session.isConnected();
33
+ }
34
+
35
+ public void startCasting(String url, String contentType, String title, List<SubtitleItem> subtitles, String activeSubtitleLabel) {
36
+ try {
37
+ CastSession session = castContext.getSessionManager().getCurrentCastSession();
38
+ if (session == null) {
39
+ Log.w(TAG, "No Cast session available");
40
+ return;
41
+ }
42
+ RemoteMediaClient rmc = session.getRemoteMediaClient();
43
+ if (rmc == null) {
44
+ Log.w(TAG, "RemoteMediaClient is null");
45
+ return;
46
+ }
47
+
48
+ MediaMetadata md = new MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE);
49
+ md.putString(MediaMetadata.KEY_TITLE, title != null ? title : "Unified Player");
50
+
51
+ MediaInfo.Builder mediaInfo = new MediaInfo.Builder(url)
52
+ .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
53
+ .setContentType(contentType)
54
+ .setMetadata(md);
55
+
56
+ // Subtitles
57
+ if (subtitles != null && !subtitles.isEmpty()) {
58
+ List<com.google.android.gms.cast.media.Track> tracks = new ArrayList<>();
59
+ long nextId = 1;
60
+ Long activeId = null;
61
+ for (SubtitleItem sub : subtitles) {
62
+ com.google.android.gms.cast.media.Track t =
63
+ new com.google.android.gms.cast.media.Track.Builder(nextId, com.google.android.gms.cast.media.Track.TYPE_TEXT)
64
+ .setSubtype(com.google.android.gms.cast.media.Track.SUBTYPE_SUBTITLES)
65
+ .setContentId(sub.url)
66
+ .setContentType(sub.contentType != null ? sub.contentType : inferSubtitleContentType(sub.url))
67
+ .setLanguage(sub.language)
68
+ .setName(sub.label != null ? sub.label : sub.language)
69
+ .build();
70
+ if (sub.label != null && sub.label.equalsIgnoreCase(activeSubtitleLabel)) {
71
+ activeId = nextId;
72
+ }
73
+ tracks.add(t);
74
+ nextId++;
75
+ }
76
+ mediaInfo.setMediaTracks(tracks);
77
+
78
+ MediaLoadRequestData.Builder load = new MediaLoadRequestData.Builder()
79
+ .setMediaInfo(mediaInfo.build())
80
+ .setAutoplay(true);
81
+ if (activeId != null) {
82
+ List<Long> activeTrackIds = new ArrayList<>();
83
+ activeTrackIds.add(activeId);
84
+ load.setActiveTrackIds(activeTrackIds);
85
+ }
86
+ rmc.load(load.build());
87
+ } else {
88
+ rmc.load(new MediaLoadRequestData.Builder()
89
+ .setMediaInfo(mediaInfo.build())
90
+ .setAutoplay(true)
91
+ .build());
92
+ }
93
+ } catch (Exception e) {
94
+ Log.e(TAG, "Cast load failed", e);
95
+ }
96
+ }
97
+
98
+ public void stopCasting() {
99
+ try {
100
+ CastSession session = castContext.getSessionManager().getCurrentCastSession();
101
+ if (session != null) {
102
+ RemoteMediaClient rmc = session.getRemoteMediaClient();
103
+ if (rmc != null) {
104
+ rmc.stop();
105
+ }
106
+ castContext.getSessionManager().endCurrentSession(true);
107
+ }
108
+ } catch (Exception ignored) {}
109
+ }
110
+
111
+ public void addSessionManagerListener(SessionManagerListener<CastSession> listener) {
112
+ try { castContext.getSessionManager().addSessionManagerListener(listener, CastSession.class); } catch (Exception ignored) {}
113
+ }
114
+
115
+ public void removeSessionManagerListener(SessionManagerListener<CastSession> listener) {
116
+ try { castContext.getSessionManager().removeSessionManagerListener(listener, CastSession.class); } catch (Exception ignored) {}
117
+ }
118
+
119
+ private String inferSubtitleContentType(String url) {
120
+ String u = url != null ? url.toLowerCase() : "";
121
+ if (u.endsWith(".vtt")) return "text/vtt";
122
+ if (u.endsWith(".srt")) return "application/x-subrip";
123
+ if (u.endsWith(".ttml") || u.endsWith(".dfxp") || u.endsWith(".xml")) return "application/ttml+xml";
124
+ return "text/vtt";
125
+ }
126
+
127
+ public static class SubtitleItem {
128
+ public final String url;
129
+ public final String language;
130
+ public final String label;
131
+ public final String contentType;
132
+
133
+ public SubtitleItem(String url, String language, String label, String contentType) {
134
+ this.url = url;
135
+ this.language = language;
136
+ this.label = label;
137
+ this.contentType = contentType;
138
+ }
139
+ }
140
+ }
141
+
@@ -0,0 +1,29 @@
1
+ package com.unifiedvideo.player.cast;
2
+
3
+ import android.content.Context;
4
+
5
+ import com.google.android.gms.cast.framework.CastOptions;
6
+ import com.google.android.gms.cast.framework.OptionsProvider;
7
+ import com.google.android.gms.cast.framework.SessionProvider;
8
+
9
+ import java.util.List;
10
+
11
+ /**
12
+ * Provides CastOptions to the Cast framework. Referenced from AndroidManifest.
13
+ */
14
+ public class CastOptionsProvider implements OptionsProvider {
15
+ @Override
16
+ public CastOptions getCastOptions(Context context) {
17
+ // Use default media receiver; apps can replace via setter in CastManager
18
+ String receiverAppId = com.google.android.gms.cast.framework.media.CastMediaOptions.DEFAULT_MEDIA_RECEIVER_APPLICATION_ID;
19
+ return new CastOptions.Builder()
20
+ .setReceiverApplicationId(receiverAppId)
21
+ .build();
22
+ }
23
+
24
+ @Override
25
+ public List<SessionProvider> getAdditionalSessionProviders(Context context) {
26
+ return null;
27
+ }
28
+ }
29
+
@@ -0,0 +1,88 @@
1
+ package com.unifiedvideo.player.overlay;
2
+
3
+ import android.content.Context;
4
+ import android.graphics.Canvas;
5
+ import android.graphics.LinearGradient;
6
+ import android.graphics.Paint;
7
+ import android.graphics.Shader;
8
+ import android.util.AttributeSet;
9
+ import android.view.View;
10
+
11
+ import java.util.Random;
12
+
13
+ /**
14
+ * Semi-random moving watermark overlay similar to web watermark.
15
+ */
16
+ public class WatermarkOverlayView extends View {
17
+ private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
18
+ private final Random random = new Random();
19
+
20
+ private int colorStart = 0xFFFF0000; // #ff0000
21
+ private int colorEnd = 0xFFFF4D4F; // #ff4d4f
22
+ private float alpha = 0.3f;
23
+
24
+ private String text = "PREMIUM";
25
+ private float x = 50;
26
+ private float y = 80;
27
+
28
+ public WatermarkOverlayView(Context context) {
29
+ super(context);
30
+ init();
31
+ }
32
+
33
+ public WatermarkOverlayView(Context context, AttributeSet attrs) {
34
+ super(context, attrs);
35
+ init();
36
+ }
37
+
38
+ public WatermarkOverlayView(Context context, AttributeSet attrs, int defStyleAttr) {
39
+ super(context, attrs, defStyleAttr);
40
+ init();
41
+ }
42
+
43
+ private void init() {
44
+ paint.setTextSize(42f);
45
+ paint.setStyle(Paint.Style.FILL);
46
+ setClickable(false);
47
+ setFocusable(false);
48
+ }
49
+
50
+ public void setAccentColors(int startColor, int endColor) {
51
+ this.colorStart = startColor;
52
+ this.colorEnd = endColor;
53
+ invalidate();
54
+ }
55
+
56
+ public void setAlphaFactor(float alphaFactor) {
57
+ this.alpha = Math.max(0f, Math.min(1f, alphaFactor));
58
+ invalidate();
59
+ }
60
+
61
+ public void setText(String text) {
62
+ this.text = text;
63
+ invalidate();
64
+ }
65
+
66
+ /** Move watermark to a random position and redraw. */
67
+ public void randomize() {
68
+ int w = getWidth();
69
+ int h = getHeight();
70
+ if (w <= 0 || h <= 0) return;
71
+ float margin = 20f;
72
+ x = margin + random.nextFloat() * Math.max(1f, (w - margin * 2 - 200));
73
+ y = margin + random.nextFloat() * Math.max(1f, (h - margin * 2 - 60));
74
+ invalidate();
75
+ }
76
+
77
+ @Override
78
+ protected void onDraw(Canvas canvas) {
79
+ super.onDraw(canvas);
80
+ int w = getWidth();
81
+ Shader shader = new LinearGradient(0, 0, w, 0, colorStart, colorEnd, Shader.TileMode.CLAMP);
82
+ paint.setShader(shader);
83
+ paint.setAlpha((int) (alpha * 255));
84
+ String content = text + " • " + System.currentTimeMillis() % 100000;
85
+ canvas.drawText(content, x, y, paint);
86
+ }
87
+ }
88
+
@@ -0,0 +1,33 @@
1
+ package com.unifiedvideo.player.pip;
2
+
3
+ import android.content.BroadcastReceiver;
4
+ import android.content.Context;
5
+ import android.content.Intent;
6
+ import android.util.Log;
7
+
8
+ import com.google.android.exoplayer2.ExoPlayer;
9
+ import com.unifiedvideo.player.services.PlayerHolder;
10
+
11
+ /**
12
+ * Receives PiP action intents to control playback.
13
+ */
14
+ public class PipActionReceiver extends BroadcastReceiver {
15
+ public static final String ACTION_PLAY = "com.unifiedvideo.player.ACTION_PLAY";
16
+ public static final String ACTION_PAUSE = "com.unifiedvideo.player.ACTION_PAUSE";
17
+
18
+ @Override
19
+ public void onReceive(Context context, Intent intent) {
20
+ if (intent == null || intent.getAction() == null) return;
21
+ ExoPlayer player = PlayerHolder.getPlayer();
22
+ if (player == null) return;
23
+ switch (intent.getAction()) {
24
+ case ACTION_PLAY:
25
+ try { player.play(); } catch (Exception e) { Log.w("PipAction", "play failed", e); }
26
+ break;
27
+ case ACTION_PAUSE:
28
+ try { player.pause(); } catch (Exception e) { Log.w("PipAction", "pause failed", e); }
29
+ break;
30
+ }
31
+ }
32
+ }
33
+
@@ -0,0 +1,110 @@
1
+ package com.unifiedvideo.player.services;
2
+
3
+ import android.app.Notification;
4
+ import android.app.NotificationChannel;
5
+ import android.app.NotificationManager;
6
+ import android.app.PendingIntent;
7
+ import android.app.Service;
8
+ import android.content.Context;
9
+ import android.content.Intent;
10
+ import android.os.Build;
11
+ import android.os.IBinder;
12
+
13
+ import androidx.annotation.Nullable;
14
+ import androidx.core.app.NotificationCompat;
15
+ import androidx.media.app.NotificationCompat.MediaStyle;
16
+ import android.support.v4.media.session.MediaSessionCompat;
17
+ import android.support.v4.media.session.PlaybackStateCompat;
18
+
19
+ import com.google.android.exoplayer2.ExoPlayer;
20
+
21
+ /**
22
+ * Foreground service for background playback. Minimal implementation.
23
+ */
24
+ public class PlaybackService extends Service {
25
+ private static final String CHANNEL_ID = "uvf_playback";
26
+ private static final int NOTIFICATION_ID = 1001;
27
+
28
+ private MediaSessionCompat mediaSession;
29
+
30
+ @Override
31
+ public void onCreate() {
32
+ super.onCreate();
33
+ mediaSession = new MediaSessionCompat(this, "UVF-Player");
34
+ mediaSession.setActive(true);
35
+ createChannel();
36
+ }
37
+
38
+ @Override
39
+ public int onStartCommand(Intent intent, int flags, int startId) {
40
+ startForeground(NOTIFICATION_ID, buildNotification());
41
+ return START_STICKY;
42
+ }
43
+
44
+ @Nullable
45
+ @Override
46
+ public IBinder onBind(Intent intent) {
47
+ return null;
48
+ }
49
+
50
+ @Override
51
+ public void onDestroy() {
52
+ super.onDestroy();
53
+ if (mediaSession != null) {
54
+ mediaSession.setActive(false);
55
+ mediaSession.release();
56
+ }
57
+ }
58
+
59
+ private Notification buildNotification() {
60
+ ExoPlayer player = PlayerHolder.getPlayer();
61
+ boolean isPlaying = player != null && player.isPlaying();
62
+
63
+ PlaybackStateCompat state = new PlaybackStateCompat.Builder()
64
+ .setActions(PlaybackStateCompat.ACTION_PLAY_PAUSE | PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_PAUSE)
65
+ .setState(isPlaying ? PlaybackStateCompat.STATE_PLAYING : PlaybackStateCompat.STATE_PAUSED, player != null ? player.getCurrentPosition() : 0, 1.0f)
66
+ .build();
67
+ mediaSession.setPlaybackState(state);
68
+
69
+ PendingIntent playIntent = PendingIntent.getBroadcast(this, 100,
70
+ new Intent("com.unifiedvideo.player.ACTION_PLAY"), Build.VERSION.SDK_INT >= 31 ? PendingIntent.FLAG_MUTABLE : 0);
71
+ PendingIntent pauseIntent = PendingIntent.getBroadcast(this, 101,
72
+ new Intent("com.unifiedvideo.player.ACTION_PAUSE"), Build.VERSION.SDK_INT >= 31 ? PendingIntent.FLAG_MUTABLE : 0);
73
+
74
+ NotificationCompat.Action playAction = new NotificationCompat.Action(android.R.drawable.ic_media_play, "Play", playIntent);
75
+ NotificationCompat.Action pauseAction = new NotificationCompat.Action(android.R.drawable.ic_media_pause, "Pause", pauseIntent);
76
+
77
+ NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID)
78
+ .setSmallIcon(android.R.drawable.ic_media_play)
79
+ .setContentTitle("Playing video")
80
+ .setContentText("Unified Video Player")
81
+ .setOngoing(isPlaying)
82
+ .addAction(isPlaying ? pauseAction : playAction)
83
+ .setStyle(new MediaStyle().setMediaSession(mediaSession.getSessionToken()).setShowActionsInCompactView(0));
84
+
85
+ return builder.build();
86
+ }
87
+
88
+ private void createChannel() {
89
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
90
+ NotificationChannel channel = new NotificationChannel(CHANNEL_ID, "Playback", NotificationManager.IMPORTANCE_LOW);
91
+ NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
92
+ nm.createNotificationChannel(channel);
93
+ }
94
+ }
95
+
96
+ // Static helpers
97
+ public static void start(Context ctx) {
98
+ Intent i = new Intent(ctx, PlaybackService.class);
99
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
100
+ ctx.startForegroundService(i);
101
+ } else {
102
+ ctx.startService(i);
103
+ }
104
+ }
105
+
106
+ public static void stop(Context ctx) {
107
+ ctx.stopService(new Intent(ctx, PlaybackService.class));
108
+ }
109
+ }
110
+
@@ -0,0 +1,19 @@
1
+ package com.unifiedvideo.player.services;
2
+
3
+ import com.google.android.exoplayer2.ExoPlayer;
4
+
5
+ /**
6
+ * Holds a reference to the current ExoPlayer for background service and PiP actions.
7
+ */
8
+ public class PlayerHolder {
9
+ private static volatile ExoPlayer player;
10
+
11
+ public static void setPlayer(ExoPlayer p) {
12
+ player = p;
13
+ }
14
+
15
+ public static ExoPlayer getPlayer() {
16
+ return player;
17
+ }
18
+ }
19
+
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@unified-video/core",
3
+ "version": "1.0.0",
4
+ "description": "Core interfaces and factory for Unified Video Framework",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist",
9
+ "src"
10
+ ],
11
+ "scripts": {
12
+ "build": "tsc -p tsconfig.json",
13
+ "watch": "tsc -p tsconfig.json --watch",
14
+ "clean": "rm -rf dist",
15
+ "test": "jest",
16
+ "lint": "eslint src --ext .ts,.tsx"
17
+ },
18
+ "keywords": [
19
+ "video",
20
+ "player",
21
+ "framework",
22
+ "core",
23
+ "interfaces"
24
+ ],
25
+ "author": "Your Company",
26
+ "license": "MIT",
27
+ "devDependencies": {
28
+ "@types/node": "^18.0.0",
29
+ "typescript": "^4.9.0"
30
+ },
31
+ "publishConfig": {
32
+ "access": "public"
33
+ }
34
+ }