refactor(android): migrate VideoEventEmitter to Kotlin (#3962)

* refactor(android): migrate VideoEventEmitter to Kotlin

* feat(android): apply rewritten EventEmitter's functions

* refactor(android): remove duplicated code

* fix(android): fix lint error

* fix(android): fix event name value

* refactor(android): rename of event constants for Fabric

- https://github.com/facebook/react-native/blob/v0.74.3/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModuleConstantsHelper.java#L136-L138
This commit is contained in:
YangJH
2024-07-04 21:01:28 +09:00
committed by GitHub
parent 7def3ac387
commit 3c9b1b571a
4 changed files with 409 additions and 548 deletions

View File

@@ -142,6 +142,7 @@ import java.lang.Math;
import java.util.List;
import java.util.ArrayList;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.Callable;
@@ -173,7 +174,7 @@ public class ReactExoplayerView extends FrameLayout implements
DEFAULT_COOKIE_MANAGER.setCookiePolicy(CookiePolicy.ACCEPT_ORIGINAL_SERVER);
}
private final VideoEventEmitter eventEmitter;
protected final VideoEventEmitter eventEmitter;
private final ReactExoplayerConfig config;
private final DefaultBandwidthMeter bandwidthMeter;
private LegacyPlayerControlView playerControlView;
@@ -280,7 +281,7 @@ public class ReactExoplayerView extends FrameLayout implements
lastPos = pos;
lastBufferDuration = bufferedDuration;
lastDuration = duration;
eventEmitter.progressChanged(pos, bufferedDuration, player.getDuration(), getPositionInFirstPeriodMsForCurrentWindow(pos));
eventEmitter.onVideoProgress.invoke(pos, bufferedDuration, player.getDuration(), getPositionInFirstPeriodMsForCurrentWindow(pos));
}
}
}
@@ -307,7 +308,7 @@ public class ReactExoplayerView extends FrameLayout implements
public ReactExoplayerView(ThemedReactContext context, ReactExoplayerConfig config) {
super(context);
this.themedReactContext = context;
this.eventEmitter = new VideoEventEmitter(context);
this.eventEmitter = new VideoEventEmitter();
this.config = config;
this.bandwidthMeter = config.getBandwidthMeter();
@@ -323,12 +324,6 @@ public class ReactExoplayerView extends FrameLayout implements
return player != null && player.isPlayingAd();
}
@Override
public void setId(int id) {
super.setId(id);
eventEmitter.setViewId(id);
}
private void createViews() {
if (CookieHandler.getDefault() != DEFAULT_COOKIE_MANAGER) {
CookieHandler.setDefault(DEFAULT_COOKIE_MANAGER);
@@ -388,13 +383,13 @@ public class ReactExoplayerView extends FrameLayout implements
public void onBandwidthSample(int elapsedMs, long bytes, long bitrate) {
if (mReportBandwidth) {
if (player == null) {
eventEmitter.bandwidthReport(bitrate, 0, 0, "-1");
eventEmitter.onVideoBandwidthUpdate.invoke(bitrate, 0, 0, "-1");
} else {
Format videoFormat = player.getVideoFormat();
int width = videoFormat != null ? videoFormat.width : 0;
int height = videoFormat != null ? videoFormat.height : 0;
String trackId = videoFormat != null ? videoFormat.id : "-1";
eventEmitter.bandwidthReport(bitrate, height, width, trackId);
eventEmitter.onVideoBandwidthUpdate.invoke(bitrate, height, width, trackId);
}
}
}
@@ -423,7 +418,7 @@ public class ReactExoplayerView extends FrameLayout implements
playerControlView.addVisibilityListener(new LegacyPlayerControlView.VisibilityListener() {
@Override
public void onVisibilityChange(int visibility) {
eventEmitter.controlsVisibilityChanged(visibility == View.VISIBLE);
eventEmitter.onControlsVisibilityChange.invoke(visibility == View.VISIBLE);
}
});
}
@@ -471,7 +466,7 @@ public class ReactExoplayerView extends FrameLayout implements
//Handling the pauseButton click event
ImageButton pauseButton = playerControlView.findViewById(R.id.exo_pause);
pauseButton.setOnClickListener((View v) ->
setPausedModifier(true)
setPausedModifier(true)
);
//Handling the fullScreenButton click event
@@ -682,7 +677,7 @@ public class ReactExoplayerView extends FrameLayout implements
}
if (activity == null) {
DebugLog.e(TAG, "Failed to initialize Player!, null activity");
eventEmitter.error("Failed to initialize Player!", new Exception("Current Activity is null!"), "1001");
eventEmitter.onVideoError.invoke("Failed to initialize Player!", new Exception("Current Activity is null!"), "1001");
return;
}
@@ -699,7 +694,7 @@ public class ReactExoplayerView extends FrameLayout implements
DebugLog.e(TAG, "Failed to initialize Player! 1");
DebugLog.e(TAG, ex.toString());
ex.printStackTrace();
self.eventEmitter.error(ex.toString(), ex, "1001");
eventEmitter.onVideoError.invoke(ex.toString(), ex, "1001");
}
});
});
@@ -711,7 +706,7 @@ public class ReactExoplayerView extends FrameLayout implements
DebugLog.e(TAG, "Failed to initialize Player! 2");
DebugLog.e(TAG, ex.toString());
ex.printStackTrace();
eventEmitter.error(ex.toString(), ex, "1001");
eventEmitter.onVideoError.invoke(ex.toString(), ex, "1001");
}
};
mainHandler.postDelayed(mainRunnable, 1);
@@ -797,7 +792,7 @@ public class ReactExoplayerView extends FrameLayout implements
int errorStringId = Util.SDK_INT < 18 ? R.string.error_drm_not_supported
: (e.reason == UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME
? R.string.error_drm_unsupported_scheme : R.string.error_drm_unknown);
eventEmitter.error(getResources().getString(errorStringId), e, "3003");
eventEmitter.onVideoError.invoke(getResources().getString(errorStringId), e, "3003");
return null;
}
}
@@ -812,7 +807,7 @@ public class ReactExoplayerView extends FrameLayout implements
if (drmSessionManager == null && drmProps != null && drmProps.getDrmUUID() != null) {
// Failed to intialize DRM session manager - cannot continue
DebugLog.e(TAG, "Failed to initialize DRM Session Manager Framework!");
eventEmitter.error("Failed to initialize DRM Session Manager Framework!", new Exception("DRM Session Manager Framework failure!"), "3003");
eventEmitter.onVideoError.invoke("Failed to initialize DRM Session Manager Framework!", new Exception("DRM Session Manager Framework failure!"), "3003");
return;
}
@@ -873,7 +868,7 @@ public class ReactExoplayerView extends FrameLayout implements
reLayoutControls();
eventEmitter.loadStart();
eventEmitter.onVideoLoadStart.invoke();
loadVideoStarted = true;
finishPlayerInitialization();
@@ -985,7 +980,7 @@ public class ReactExoplayerView extends FrameLayout implements
return buildDrmSessionManager(uuid, licenseUrl, keyRequestPropertiesArray, ++retryCount);
}
// Handle the unknow exception and emit to JS
eventEmitter.error(ex.toString(), ex, "3006");
eventEmitter.onVideoError.invoke(ex.toString(), ex, "3006");
return null;
}
}
@@ -1193,7 +1188,7 @@ public class ReactExoplayerView extends FrameLayout implements
switch (focusChange) {
case AudioManager.AUDIOFOCUS_LOSS:
view.hasAudioFocus = false;
view.eventEmitter.audioFocusChanged(false);
view.eventEmitter.onAudioFocusChanged.invoke(false);
// FIXME this pause can cause issue if content doesn't have pause capability (can happen on live channel)
if (activity != null) {
activity.runOnUiThread(view::pausePlayback);
@@ -1201,11 +1196,11 @@ public class ReactExoplayerView extends FrameLayout implements
view.audioManager.abandonAudioFocus(this);
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
view.eventEmitter.audioFocusChanged(false);
view.eventEmitter.onAudioFocusChanged.invoke(false);
break;
case AudioManager.AUDIOFOCUS_GAIN:
view.hasAudioFocus = true;
view.eventEmitter.audioFocusChanged(true);
view.eventEmitter.onAudioFocusChanged.invoke(true);
break;
default:
break;
@@ -1216,14 +1211,14 @@ public class ReactExoplayerView extends FrameLayout implements
// Lower the volume
if (!view.muted) {
activity.runOnUiThread(() ->
view.player.setVolume(view.audioVolume * 0.8f)
view.player.setVolume(view.audioVolume * 0.8f)
);
}
} else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
// Raise it back to normal
if (!view.muted) {
activity.runOnUiThread(() ->
view.player.setVolume(view.audioVolume * 1)
view.player.setVolume(view.audioVolume * 1)
);
}
}
@@ -1326,7 +1321,7 @@ public class ReactExoplayerView extends FrameLayout implements
// AudioBecomingNoisyListener implementation
@Override
public void onAudioBecomingNoisy() {
eventEmitter.audioBecomingNoisy();
eventEmitter.onVideoAudioBecomingNoisy.invoke();
}
// Player.Listener implementation
@@ -1341,11 +1336,11 @@ public class ReactExoplayerView extends FrameLayout implements
int playbackState = player.getPlaybackState();
boolean playWhenReady = player.getPlayWhenReady();
String text = "onStateChanged: playWhenReady=" + playWhenReady + ", playbackState=";
eventEmitter.playbackRateChange(playWhenReady && playbackState == ExoPlayer.STATE_READY ? 1.0f : 0.0f);
eventEmitter.onPlaybackRateChange.invoke(playWhenReady && playbackState == ExoPlayer.STATE_READY ? 1.0f : 0.0f);
switch (playbackState) {
case Player.STATE_IDLE:
text += "idle";
eventEmitter.idle();
eventEmitter.onVideoIdle.invoke();
clearProgressMessageHandler();
if (!player.getPlayWhenReady()) {
setKeepScreenOn(false);
@@ -1359,7 +1354,7 @@ public class ReactExoplayerView extends FrameLayout implements
break;
case Player.STATE_READY:
text += "ready";
eventEmitter.ready();
eventEmitter.onReadyForDisplay.invoke();
onBuffering(false);
clearProgressMessageHandler(); // ensure there is no other message
startProgressHandler();
@@ -1377,7 +1372,7 @@ public class ReactExoplayerView extends FrameLayout implements
case Player.STATE_ENDED:
text += "ended";
updateProgress();
eventEmitter.end();
eventEmitter.onVideoEnd.invoke();
onStopPlayback();
setKeepScreenOn(false);
break;
@@ -1434,7 +1429,7 @@ public class ReactExoplayerView extends FrameLayout implements
if (videoTracks != null) {
isUsingContentResolution = true;
}
eventEmitter.load(duration, currentPosition, width, height,
eventEmitter.onVideoLoad.invoke(duration, currentPosition, width, height,
audioTracks, textTracks, videoTracks, trackId );
});
@@ -1443,7 +1438,7 @@ public class ReactExoplayerView extends FrameLayout implements
ArrayList<VideoTrack> videoTracks = getVideoTrackInfo();
eventEmitter.load(duration, currentPosition, width, height,
eventEmitter.onVideoLoad.invoke(duration, currentPosition, width, height,
audioTracks, textTracks, videoTracks, trackId);
}
}
@@ -1626,13 +1621,13 @@ public class ReactExoplayerView extends FrameLayout implements
}
isBuffering = buffering;
eventEmitter.buffering(buffering);
eventEmitter.onVideoBuffer.invoke(buffering);
}
@Override
public void onPositionDiscontinuity(@NonNull Player.PositionInfo oldPosition, @NonNull Player.PositionInfo newPosition, @Player.DiscontinuityReason int reason) {
if (reason == Player.DISCONTINUITY_REASON_SEEK) {
eventEmitter.seek(player.getCurrentPosition(), newPosition.positionMs % 1000); // time are in seconds /°\
eventEmitter.onVideoSeek.invoke(player.getCurrentPosition(), newPosition.positionMs % 1000); // time are in seconds /°\
if (isUsingContentResolution) {
// We need to update the selected track to make sure that it still matches user selection if track list has changed in this period
setSelectedTrack(C.TRACK_TYPE_VIDEO, videoTrackType, videoTrackValue);
@@ -1655,7 +1650,7 @@ public class ReactExoplayerView extends FrameLayout implements
if (reason == Player.DISCONTINUITY_REASON_AUTO_TRANSITION
&& player.getRepeatMode() == Player.REPEAT_MODE_ONE) {
updateProgress();
eventEmitter.end();
eventEmitter.onVideoEnd.invoke();
}
}
@@ -1666,24 +1661,24 @@ public class ReactExoplayerView extends FrameLayout implements
@Override
public void onTracksChanged(@NonNull Tracks tracks) {
eventEmitter.textTracks(getTextTrackInfo());
eventEmitter.audioTracks(getAudioTrackInfo());
eventEmitter.videoTracks(getVideoTrackInfo());
eventEmitter.onTextTracks.invoke(getTextTrackInfo());
eventEmitter.onAudioTracks.invoke(getAudioTrackInfo());
eventEmitter.onVideoTracks.invoke(getVideoTrackInfo());
}
@Override
public void onPlaybackParametersChanged(PlaybackParameters params) {
eventEmitter.playbackRateChange(params.speed);
eventEmitter.onPlaybackRateChange.invoke(params.speed);
}
@Override
public void onVolumeChanged(float volume) {
eventEmitter.volumeChange(volume);
eventEmitter.onVolumeChange.invoke(volume);
}
@Override
public void onIsPlayingChanged(boolean isPlaying) {
eventEmitter.playbackStateChanged(isPlaying);
eventEmitter.onVideoPlaybackStateChanged.invoke(isPlaying);
}
@Override
@@ -1709,7 +1704,7 @@ public class ReactExoplayerView extends FrameLayout implements
default:
break;
}
eventEmitter.error(errorString, e, errorCode);
eventEmitter.onVideoError.invoke(errorString, e, errorCode);
playerNeedsSource = true;
if (isBehindLiveWindow(e)) {
clearResumePosition();
@@ -1760,13 +1755,13 @@ public class ReactExoplayerView extends FrameLayout implements
DebugLog.d(TAG, "unhandled metadata " + entry);
}
}
eventEmitter.timedMetadata(metadataArray);
eventEmitter.onTimedMetadata.invoke(metadataArray);
}
public void onCues(CueGroup cueGroup) {
if (!cueGroup.cues.isEmpty() && cueGroup.cues.get(0).text != null) {
String subtitleText = cueGroup.cues.get(0).text.toString();
eventEmitter.textTrackDataChanged(subtitleText);
eventEmitter.onTextTrackDataChanged.invoke(subtitleText);
}
}
@@ -2220,7 +2215,7 @@ public class ReactExoplayerView extends FrameLayout implements
Window window = activity.getWindow();
WindowInsetsControllerCompat controller = new WindowInsetsControllerCompat(window, window.getDecorView());
if (isFullscreen) {
eventEmitter.fullscreenWillPresent();
eventEmitter.onVideoFullscreenPlayerWillPresent.invoke();
if (fullScreenPlayerView != null) {
fullScreenPlayerView.show();
}
@@ -2228,10 +2223,10 @@ public class ReactExoplayerView extends FrameLayout implements
WindowCompat.setDecorFitsSystemWindows(window, false);
controller.hide(WindowInsetsCompat.Type.systemBars());
controller.setSystemBarsBehavior(WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
eventEmitter.fullscreenDidPresent();
eventEmitter.onVideoFullscreenPlayerDidPresent.invoke();
});
} else {
eventEmitter.fullscreenWillDismiss();
eventEmitter.onVideoFullscreenPlayerWillDismiss.invoke();
if (fullScreenPlayerView != null) {
fullScreenPlayerView.dismiss();
reLayoutControls();
@@ -2240,7 +2235,7 @@ public class ReactExoplayerView extends FrameLayout implements
UiThreadUtil.runOnUiThread(() -> {
WindowCompat.setDecorFitsSystemWindows(window, true);
controller.show(WindowInsetsCompat.Type.systemBars());
eventEmitter.fullscreenDidDismiss();
eventEmitter.onVideoFullscreenPlayerDidDismiss.invoke();
});
}
// need to be done at the end to avoid hiding fullscreen control button when fullScreenPlayerView is shown
@@ -2291,7 +2286,7 @@ public class ReactExoplayerView extends FrameLayout implements
@Override
public void onDrmSessionManagerError(int windowIndex, MediaSource.MediaPeriodId mediaPeriodId, @NonNull Exception e) {
DebugLog.d("DRM Info", "onDrmSessionManagerError");
eventEmitter.error("onDrmSessionManagerError", e, "3002");
eventEmitter.onVideoError.invoke("onDrmSessionManagerError", e, "3002");
}
@Override
@@ -2333,16 +2328,21 @@ public class ReactExoplayerView extends FrameLayout implements
@Override
public void onAdEvent(AdEvent adEvent) {
if (adEvent.getAdData() != null) {
eventEmitter.receiveAdEvent(adEvent.getType().name(), adEvent.getAdData());
eventEmitter.onReceiveAdEvent.invoke(adEvent.getType().name(), adEvent.getAdData());
} else {
eventEmitter.receiveAdEvent(adEvent.getType().name());
eventEmitter.onReceiveAdEvent.invoke(adEvent.getType().name(), null);
}
}
@Override
public void onAdError(AdErrorEvent adErrorEvent) {
AdError error = adErrorEvent.getError();
eventEmitter.receiveAdErrorEvent(error.getMessage(), String.valueOf(error.getErrorCode()), String.valueOf(error.getErrorType()));
Map<String, String> errMap = Map.of(
"message", error.getMessage(),
"code", String.valueOf(error.getErrorCode()),
"type", String.valueOf(error.getErrorType())
);
eventEmitter.onReceiveAdEvent.invoke("ERROR", errMap);
}
public void setControlsStyles(ControlsConfig controlsStyles) {

View File

@@ -18,13 +18,12 @@ import com.brentvatne.common.api.SideLoadedTextTrackList;
import com.brentvatne.common.api.Source;
import com.brentvatne.common.api.SubtitleStyle;
import com.brentvatne.common.api.ViewType;
import com.brentvatne.common.react.VideoEventEmitter;
import com.brentvatne.common.react.EventTypes;
import com.brentvatne.common.toolbox.DebugLog;
import com.brentvatne.common.toolbox.ReactBridgeUtils;
import com.brentvatne.react.ReactNativeVideoManager;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewGroupManager;
import com.facebook.react.uimanager.annotations.ReactProp;
@@ -110,11 +109,13 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
@Override
public @Nullable Map<String, Object> getExportedCustomDirectEventTypeConstants() {
MapBuilder.Builder<String, Object> builder = MapBuilder.builder();
for (String event : VideoEventEmitter.Events) {
builder.put(event, MapBuilder.of("registrationName", event));
}
return builder.build();
return EventTypes.Companion.toMap();
}
@Override
public void addEventEmitters(@NonNull ThemedReactContext reactContext, @NonNull ReactExoplayerView view) {
super.addEventEmitters(reactContext, view);
view.eventEmitter.addEventEmitters(reactContext, view);
}
@ReactProp(name = PROP_DRM)