Skip to content

Commit

Permalink
enhancement: Add support for configurable video watermarking (#759)
Browse files Browse the repository at this point in the history
- Added `videoWatermarkType` and `videoWatermarkPosition` to `InstituteSettings`.
- Implemented dynamic and static watermark overlays in `ExoPlayerUtil`.
- Supports configurable positions and enhances content security.
  • Loading branch information
PruthiviRaj27 authored Dec 9, 2024
1 parent 0f736f3 commit 2f05693
Show file tree
Hide file tree
Showing 4 changed files with 243 additions and 93 deletions.
20 changes: 20 additions & 0 deletions core/src/main/java/in/testpress/models/InstituteSettings.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ public class InstituteSettings {
private Boolean disableImageFullscreenZoomInExam;
private Boolean enableOfflineExam;
private Boolean showOfflineExamEndingAlert;
private String videoWatermarkType;
private String videoWatermarkPosition;

public InstituteSettings(String baseUrl) {
setBaseUrl(baseUrl);
Expand Down Expand Up @@ -452,4 +454,22 @@ public InstituteSettings setShowOfflineExamEndingAlert(Boolean showOfflineExamEn
public String getDomainUrl() {
return (whiteLabeledHostUrl != null && !whiteLabeledHostUrl.isEmpty()) ? whiteLabeledHostUrl : baseUrl;
}

public String getVideoWatermarkType() {
return videoWatermarkType;
}

public InstituteSettings setVideoWatermarkType(String videoWatermarkType) {
this.videoWatermarkType = videoWatermarkType;
return this;
}

public String getVideoWatermarkPosition() {
return videoWatermarkPosition;
}

public InstituteSettings setVideoWatermarkPosition(String videoWatermarkPosition) {
this.videoWatermarkPosition = videoWatermarkPosition;
return this;
}
}
170 changes: 93 additions & 77 deletions course/src/main/java/in/testpress/course/util/ExoPlayerUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,9 @@
import android.content.res.Configuration;
import android.media.AudioManager;
import android.media.MediaCodec;
import android.os.Handler;
import android.view.ScaleGestureDetector;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.AdapterView;
import android.widget.FrameLayout;
import android.widget.ImageView;
Expand Down Expand Up @@ -66,7 +63,6 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;

import in.testpress.core.TestpressCallback;
import in.testpress.core.TestpressException;
Expand Down Expand Up @@ -102,17 +98,13 @@
import org.greenrobot.greendao.annotation.NotNull;

public class ExoPlayerUtil implements VideoTimeRangeListener, DrmSessionManagerProvider {

private static final int OVERLAY_POSITION_CHANGE_INTERVAL = 15000; // 15s
private static final int SEEK_TIME_IN_MILLISECOND = 15000; //15s

private FrameLayout exoPlayerMainFrame;
private View exoPlayerLayout;
private DoubleTapPlayerView playerView;
private LottieAnimationView progressBar;
private TextView errorMessageTextView;
private LinearLayout emailIdLayout;
private TextView emailIdTextView;
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
public ExoPlayer player;
private ImageView fullscreenIcon;
Expand Down Expand Up @@ -140,14 +132,6 @@ public class ExoPlayerUtil implements VideoTimeRangeListener, DrmSessionManagerP
private MediaRouter mediaRouter;
private MediaRouteSelector mediaRouteSelector;
private MediaRouter.Callback mediaRouterCallback;
private Handler overlayPositionHandler;
private Runnable overlayPositionChangeTask = new Runnable() {
@Override
public void run() {
displayOverlayText();
overlayPositionHandler.postDelayed(this, OVERLAY_POSITION_CHANGE_INTERVAL);
}
};
private boolean fullscreen = false;
private boolean errorOnVideoAttemptUpdate;
private int drmLicenseRetries = 0;
Expand All @@ -161,6 +145,8 @@ public void run() {
private ScaleGestureDetector scaleGestureDetector;
private PinchToZoomGesture pinchToZoomGesture;
private LiveStreamCallbackListener liveStreamCallbackListener;
boolean isDynamic = false;
private TestpressSession session;

public ExoPlayerUtil(Activity activity, FrameLayout exoPlayerMainFrame, String url,
float startPosition, LiveStreamCallbackListener liveStreamCallbackListener) {
Expand All @@ -175,17 +161,14 @@ public ExoPlayerUtil(Activity activity, FrameLayout exoPlayerMainFrame, String u
this.exoPlayerMainFrame = exoPlayerMainFrame;
this.url = url;
this.startPosition = startPosition;
session = TestpressSdk.getTestpressSession(activity);

initializeViews();
exoPlayerLayout = exoPlayerMainFrame.findViewById(R.id.exo_player_layout);
playerView = exoPlayerMainFrame.findViewById(R.id.exo_player_view);
fullscreenIcon = exoPlayerMainFrame.findViewById(R.id.exo_fullscreen_icon);
progressBar = exoPlayerMainFrame.findViewById(R.id.exo_player_progress);
errorMessageTextView = exoPlayerMainFrame.findViewById(R.id.error_message);
TestpressSession session = TestpressSdk.getTestpressSession(activity);
if (session != null && session.getInstituteSettings().isDisplayUserEmailOnVideo()) {
setUserEmailOverlay();
}
speedRateSpinner = exoPlayerMainFrame.findViewById(R.id.exo_speed_rate_spinner);
String[] speedValues = activity.getResources().getStringArray(R.array.exo_speed_values);
speedSpinnerAdapter =
Expand Down Expand Up @@ -225,6 +208,7 @@ public void onNothingSelected(AdapterView<?> parent) {
}
preventScreenshot();
hideLiveStreamNotStartedScreen();
initWatermarkOverlay();
}

private void preventScreenshot() {
Expand All @@ -247,13 +231,98 @@ public ExoPlayerUtil(Activity activity, FrameLayout exoPlayerMainFrame, String u
}

private void initializeViews() {
emailIdTextView = exoPlayerMainFrame.findViewById(R.id.email_id);
emailIdLayout = exoPlayerMainFrame.findViewById(R.id.email_id_layout);
youtubeOverlay = activity.findViewById(R.id.youtube_overlay);
noticeScreen = exoPlayerMainFrame.findViewById(R.id.notice_screen);
noticeMessage = exoPlayerMainFrame.findViewById(R.id.notice_message);
}

private void initWatermarkOverlay() {
if (session == null || session.getInstituteSettings().getVideoWatermarkType() == null) {
return;
}

String watermarkType = session.getInstituteSettings().getVideoWatermarkType();
isDynamic = watermarkType.equals("Dynamic");
if (!watermarkType.equals("Dynamic") && !watermarkType.equals("Static")) {
return;
}

ProfileDetails profileDetails = TestpressUserDetails.getInstance().getProfileDetails();

if (profileDetails == null) {
fetchProfileDetails();
} else {
addWatermarkOverlay(profileDetails);
}
}

private void fetchProfileDetails() {
TestpressUserDetails.getInstance().load(activity, new TestpressCallback<ProfileDetails>() {
@Override
public void onSuccess(ProfileDetails userDetails) {
addWatermarkOverlay(userDetails);
}

@Override
public void onException(TestpressException exception) {
Log.e("Watermark", "Failed to load user details", exception);
}
});
}

private void addWatermarkOverlay(ProfileDetails profileDetails) {
String overlayText = (profileDetails.getEmail() != null && !profileDetails.getEmail().isEmpty())
? profileDetails.getEmail()
: profileDetails.getUsername();

FrameLayout parentLayout = getOrCreateParentLayout();
WatermarkOverlay watermark = createWatermark(overlayText);

parentLayout.addView(watermark);
}

private FrameLayout getOrCreateParentLayout() {
FrameLayout parent;

if (exoPlayerLayout.getParent() instanceof FrameLayout) {
Log.d("TAG", "Using existing parent layout");
parent = (FrameLayout) exoPlayerLayout.getParent();
} else {
Log.d("TAG", "Creating new parent layout");
parent = new FrameLayout(activity);
ViewGroup.LayoutParams layoutParams = exoPlayerLayout.getLayoutParams();
parent.setLayoutParams(layoutParams);

exoPlayerLayout.setLayoutParams(new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT
));

parent.addView(exoPlayerLayout);
}

return parent;
}

private WatermarkOverlay createWatermark(String text) {
WatermarkOverlay watermark = new WatermarkOverlay(activity);
watermark.setWatermark(text);

if (isDynamic) {
watermark.setDynamicWatermark();
} else {
String position = getVideoWatermarkPosition();
watermark.setStaticWatermark(position);
}
return watermark;
}

private String getVideoWatermarkPosition() {
return session.getInstituteSettings().getVideoWatermarkPosition() != null
? session.getInstituteSettings().getVideoWatermarkPosition()
: "top-right";
}

private void initFullscreenDialog() {
fullscreenDialog = new Dialog(activity, android.R.style.Theme_Black_NoTitleBar_Fullscreen) {
public void onBackPressed() {
Expand Down Expand Up @@ -318,7 +387,6 @@ public void initializePlayer() {
}
preparePlayer();
player.seekTo(getStartPositionInMilliSeconds());
initializeUsernameOverlay();
registerListeners();
}

Expand Down Expand Up @@ -406,13 +474,6 @@ private void preparePlayer() {
player.prepare();
}

private void initializeUsernameOverlay() {
if (overlayPositionHandler != null) {
overlayPositionHandler
.postDelayed(overlayPositionChangeTask, OVERLAY_POSITION_CHANGE_INTERVAL);
}
}

private void registerListeners() {
registerUsbConnectionStateReceiver();
}
Expand Down Expand Up @@ -458,9 +519,6 @@ public void releasePlayer() {
playWhenReady = player.getPlayWhenReady();
player.release();
player = null;
if (overlayPositionHandler != null) {
overlayPositionHandler.removeCallbacks(overlayPositionChangeTask);
}
}
if (usbConnectionStateReceiver != null) {
try {
Expand Down Expand Up @@ -550,49 +608,6 @@ public void onStop() {
}
}

private void setUserEmailOverlay() {
ProfileDetails profileDetails = TestpressUserDetails.getInstance().getProfileDetails();
if (profileDetails != null) {
setUserEmailOverlay(profileDetails);
} else {
TestpressUserDetails.getInstance().load(activity, new TestpressCallback<ProfileDetails>() {
@Override
public void onSuccess(ProfileDetails userDetails) {
setUserEmailOverlay(userDetails);
}

@Override
public void onException(TestpressException exception) {
}
});
}
}

private void setUserEmailOverlay(ProfileDetails profileDetails) {
String overlayText;
if (profileDetails.getEmail() != null && !profileDetails.getEmail().isEmpty()) {
overlayText = profileDetails.getEmail();
} else {
overlayText = profileDetails.getUsername();
}
emailIdTextView.setText(overlayText);
overlayPositionHandler = new Handler();
startOverlayMarquee();
}

private void startOverlayMarquee() {
Animation marquee = AnimationUtils.loadAnimation(activity, R.anim.testpress_marquee);
emailIdLayout.startAnimation(marquee);
}

private void displayOverlayText() {
int height = Math.max(emailIdLayout.getMeasuredHeight(), 1);
Random random = new Random();
float randomY = random.nextInt(height) + emailIdLayout.getY();
emailIdTextView.setY(randomY);
startOverlayMarquee();
}

private void openFullscreenDialog() {

if (!isopenFullscreenDialogCalled) {
Expand All @@ -603,6 +618,7 @@ private void openFullscreenDialog() {
setFullscreenIcon(R.drawable.testpress_fullscreen_exit);
hideSystemBars();
addPinchToZoom();
initWatermarkOverlay();
}
}

Expand Down Expand Up @@ -641,6 +657,7 @@ private void closeFullscreenDialog() {
removePlayerViewFromDialog();
setFullscreenIcon(R.drawable.testpress_fullscreen);
removePinchToZoom();
initWatermarkOverlay();
}
}

Expand All @@ -658,7 +675,6 @@ private void removePlayerViewFromDialog() {

private void setFullscreenIcon(@DrawableRes int imageResId) {
fullscreenIcon.setImageDrawable(ContextCompat.getDrawable(activity, imageResId));
startOverlayMarquee();
}

Map<String, Object> getVideoAttemptParameters() {
Expand Down
Loading

0 comments on commit 2f05693

Please sign in to comment.