Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Gh 101 implement set playlist item callback #128

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -581,7 +581,7 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native/ReactCommon/yoga"

SPEC CHECKSUMS:
boost: 9fa78656d705f55b1220151d997e57e2a3f2cde0
boost: a7c83b31436843459a1961bfd74b96033dc77234
CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99
DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54
FBLazyVector: efad4471d02263013cfcb7a2f75de6ac7565692f
Expand Down Expand Up @@ -644,6 +644,6 @@ SPEC CHECKSUMS:
Yoga: 56413d530d1808044600320ced5baa883acedc44
YogaKit: f782866e155069a2cca2517aafea43200b01fd5a

PODFILE CHECKSUM: b82cb7634d7fae97c4001739522f654fd256489b
PODFILE CHECKSUM: 971e2b8340709a17d29721d9a2de86e290851d46

COCOAPODS: 1.15.2
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

package com.jwplayer.rnjwplayer;

import com.facebook.react.bridge.Arguments;
Expand All @@ -7,6 +6,7 @@
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.IllegalViewOperationException;
Expand Down Expand Up @@ -556,6 +556,23 @@ public void execute (NativeViewHierarchyManager nvhm) {
}
}

@ReactMethod
public void resolveNextPlaylistItem(final int reactTag, final ReadableMap playlistItem) {
try {
UIManagerModule uiManager = mReactContext.getNativeModule(UIManagerModule.class);
uiManager.addUIBlock(new UIBlock() {
public void execute(NativeViewHierarchyManager nvhm) {
RNJWPlayerView playerView = (RNJWPlayerView) nvhm.resolveView(reactTag);

if (playerView != null) {
playerView.resolveNextPlaylistItem(playlistItem);
}
}
});
} catch (IllegalViewOperationException e) {
throw e;
}
}

private int stateToInt(PlayerState playerState) {
switch (playerState) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@normtronics not needed.


public class RNJWPlayerView extends RelativeLayout implements
VideoPlayerEvents.OnFullscreenListener,
Expand Down Expand Up @@ -156,6 +157,7 @@ public class RNJWPlayerView extends RelativeLayout implements
VideoPlayerEvents.OnCaptionsListListener,
VideoPlayerEvents.OnCaptionsChangedListener,
VideoPlayerEvents.OnMetaListener,
VideoPlayerEvents.PlaylistItemCallbackListener,

CastingEvents.OnCastListener,

Expand Down Expand Up @@ -236,6 +238,9 @@ public class RNJWPlayerView extends RelativeLayout implements
private MediaServiceController mMediaServiceController;
private PipHandlerReceiver mReceiver = null;

// Add completion handler field
PlaylistItemDecision itemUpdatePromise = null;

private void doBindService() {
if (mMediaServiceController != null) {
if (!isBackgroundAudioServiceRunning()) {
Expand Down Expand Up @@ -521,9 +526,30 @@ public void setupPlayerView(Boolean backgroundAudioEnabled) {
} else {
mPlayer.setFullscreenHandler(new fullscreenHandler());
}

mPlayer.allowBackgroundAudio(backgroundAudioEnabled);
mPlayer.setPlaylistItemCallbackListener(this);
Copy link
Contributor

@hunyoboy hunyoboy Feb 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@normtronics this requires all lib. users to use onBeforeNextPlaylistItem on the js side. in fact, it'll never load the playlist items after the first one if they don't use that callback and resolve back the playlistItems. the logic i think should be - call this line only if onBeforeNextPlaylistItem is set on the js side. @Jmilham21 any idea how we can cleanly achieve this?

it's not big of a deal on ios but its safer/cleaner if the same logic is applied as well.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}
}

public void resolveNextPlaylistItem(ReadableMap playlistItem) {
if (itemUpdatePromise == null) {
return;
}

if (playlistItem == null) {
itemUpdatePromise.continuePlayback();
itemUpdatePromise = null;
return;
}

try {
PlaylistItem updatedPlaylistItem = Util.getPlaylistItem(playlistItem);
itemUpdatePromise.modify(updatedPlaylistItem);
} catch (Exception exception) {
itemUpdatePromise.continuePlayback();
}

itemUpdatePromise = null;
}

/**
Expand Down Expand Up @@ -627,6 +653,18 @@ public void onAllowFullscreenPortrait(boolean allowFullscreenPortrait) {
return delegate;
}

@Override
public void onBeforeNextPlaylistItem(PlaylistItemDecision PlaylistItemDecision, PlaylistItem nextItem, int indexOfNextItem) {
WritableMap event = Arguments.createMap();
Gson gson = new Gson();
event.putString("message", "onBeforeNextPlaylistItem");
event.putInt("index", indexOfNextItem);
event.putString("playlistItem", gson.toJson(nextItem));
getReactContext().getJSModule(RCTEventEmitter.class).receiveEvent(getId(), "topBeforeNextPlaylistItem", event);

itemUpdatePromise = PlaylistItemDecision;
}

private class fullscreenHandler implements FullscreenHandler {
ViewGroup mPlayerViewContainer = (ViewGroup) mPlayerView.getParent();
private View mDecorView;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

package com.jwplayer.rnjwplayer;

import com.facebook.react.bridge.ReactApplicationContext;
Expand Down Expand Up @@ -171,6 +170,10 @@ public Map getExportedCustomBubblingEventTypeConstants() {
MapBuilder.of(
"phasedRegistrationNames",
MapBuilder.of("bubbled", "onLoaded")))
.put("topBeforeNextPlaylistItem",
MapBuilder.of(
"phasedRegistrationNames",
MapBuilder.of("bubbled", "onBeforeNextPlaylistItem")))
.build();
}

Expand Down
2 changes: 2 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -570,6 +570,7 @@ declare module "@jwplayer/jwplayer-react-native" {
onCaptionsList?: (event: BaseEvent<CaptionsListEventProps>) => void;
onAudioTracks?: () => void;
shouldComponentUpdate?: (nextProps: any, nextState: any) => boolean;
onBeforeNextPlaylistItem?: (event: BaseEvent<PlaylistItemEventProps>) => void;
}

export default class JWPlayer extends React.Component<PropsType> {
Expand Down Expand Up @@ -601,5 +602,6 @@ declare module "@jwplayer/jwplayer-react-native" {
setCurrentCaptions(index: number): void;
getCurrentCaptions(): Promise<number | null>;
setVisibility(visibility: boolean, controls: JWControlType[]): void;
resolveNextPlaylistItem(playlistItem: PlaylistItem | JwPlaylistItem): void;
}
}
11 changes: 11 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,8 @@ export default class JWPlayer extends Component {
onCaptionsChanged: PropTypes.func,
onCaptionsList: PropTypes.func,
onAudioTracks: PropTypes.func,
onBeforeNextPlaylistItem: PropTypes.func,
resolveNextPlaylistItem: PropTypes.func
};

constructor(props) {
Expand Down Expand Up @@ -706,6 +708,15 @@ export default class JWPlayer extends Component {
}
}

resolveNextPlaylistItem(playlistItem) {
if (RNJWPlayerManager) {
RNJWPlayerManager.resolveNextPlaylistItem(
this.getRNJWPlayerBridgeHandle(),
playlistItem
);
}
}

getRNJWPlayerBridgeHandle() {
return findNodeHandle(this[this.ref_key]);
}
Expand Down
45 changes: 45 additions & 0 deletions ios/RNJWPlayer/RNJWPlayerView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class RNJWPlayerView : UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDele
var castController: JWCastController!
var isCasting: Bool = false
var availableDevices: [AnyObject]!
var onBeforeNextPlaylistItemCompletion: ((JWPlayerItem?) -> ())?

@objc var onBuffer: RCTDirectEventBlock?
@objc var onUpdateBuffer: RCTDirectEventBlock?
Expand Down Expand Up @@ -87,6 +88,7 @@ class RNJWPlayerView : UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDele
@objc var onCastingFailed: RCTDirectEventBlock?
@objc var onCaptionsChanged: RCTDirectEventBlock?
@objc var onCaptionsList: RCTDirectEventBlock?
@objc var onBeforeNextPlaylistItem: RCTDirectEventBlock?

init() {
super.init(frame: CGRect(x: 20, y: 0, width: UIScreen.main.bounds.width - 40, height: 300))
Expand Down Expand Up @@ -348,6 +350,8 @@ class RNJWPlayerView : UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDele
self.setupPlayerViewController(config: config, playerConfig: jwConfig!)
}
}

self.setupPlaylistItemCallback()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@normtronics same idea on android. please set only when onBeforeNextPlaylistItem callback is set on the js side.

} catch {
print(error)
}
Expand All @@ -362,6 +366,47 @@ class RNJWPlayerView : UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDele
}
}

func setupPlaylistItemCallback() {
playerViewController.player.setPlaylistItemCallback { [weak self] item, index, completion in
print("setPlaylistItemCallback called with index \(index)")
guard let self = self else {
print("setPlaylistItemCallback: self is nil, resuming normally")
completion(item)
return
}

if let onBeforeNextPlaylistItem = self.onBeforeNextPlaylistItem {
print("Storing completion handler and triggering onBeforeNextPlaylistItem")
// Store the completion handler first, before any other operations
self.onBeforeNextPlaylistItemCompletion = completion
print("Completion handler stored: \(self.onBeforeNextPlaylistItemCompletion != nil)")

do {
let data = try JSONSerialization.data(withJSONObject: item.toJSONObject(), options: [.prettyPrinted])
let jsonString = String(data: data, encoding: .utf8) ?? "{}"

print("Triggering onBeforeNextPlaylistItem with index \(index)")
print("Completion handler before event: \(self.onBeforeNextPlaylistItemCompletion != nil)")

// Pass the playlist item to the React Native side
onBeforeNextPlaylistItem([
"playlistItem": jsonString,
"index": index
])
print("Completion handler after event: \(self.onBeforeNextPlaylistItemCompletion != nil)")

} catch {
print("Error serializing playlist item: \(error)")
self.onBeforeNextPlaylistItemCompletion?(item) // Call completion handler directly on error
self.onBeforeNextPlaylistItemCompletion = nil
}
} else {
print("No onBeforeNextPlaylistItem handler set, calling completion directly")
completion(item)
}
}
}

// MARK: - RNJWPlayer styling

func colorWithHexString(hex:String!) -> UIColor! {
Expand Down
3 changes: 3 additions & 0 deletions ios/RNJWPlayer/RNJWPlayerViewManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ @interface RCT_EXTERN_MODULE(RNJWPlayerViewManager, RCTViewManager)
RCT_EXPORT_VIEW_PROPERTY(onSetupPlayerError, RCTDirectEventBlock);
RCT_EXPORT_VIEW_PROPERTY(onPlayerError, RCTDirectEventBlock);
RCT_EXPORT_VIEW_PROPERTY(onPlayerWarning, RCTDirectEventBlock);
RCT_EXPORT_VIEW_PROPERTY(onBeforeNextPlaylistItem, RCTDirectEventBlock);

/* ad events */
RCT_EXPORT_VIEW_PROPERTY(onPlayerAdWarning, RCTDirectEventBlock);
Expand Down Expand Up @@ -95,6 +96,8 @@ @interface RCT_EXTERN_MODULE(RNJWPlayerViewManager, RCTViewManager)

RCT_EXTERN_METHOD(togglePIP: (nonnull NSNumber *)reactTag)

RCT_EXTERN_METHOD(resolveNextPlaylistItem: (nonnull NSNumber *)reactTag :(nonnull NSDictionary *)playlistItem)

#if USE_GOOGLE_CAST

RCT_EXTERN_METHOD(setUpCastController: (nonnull NSNumber *)reactTag)
Expand Down
21 changes: 21 additions & 0 deletions ios/RNJWPlayer/RNJWPlayerViewManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,27 @@ class RNJWPlayerViewManager: RCTViewManager {
}
}

@objc func resolveNextPlaylistItem(_ reactTag: NSNumber, _ playlistItem: NSDictionary) {
self.bridge.uiManager.addUIBlock { uiManager, viewRegistry in
guard let view = viewRegistry?[reactTag] as? RNJWPlayerView else {
print("Invalid view returned from registry, expecting RNJWPlayerView, got: \(String(describing: viewRegistry?[reactTag]))")
return
}

if let completion = view.onBeforeNextPlaylistItemCompletion {
do {
let item = try view.getPlayerItem(item: playlistItem as! [String: Any])
completion(item)
} catch {
print("Error creating JWPlayerItem: \(error)")
}
view.onBeforeNextPlaylistItemCompletion = nil
} else {
print("Warning: resolveNextPlaylistItem called but no completion handler was set")
}
}
}

#if USE_GOOGLE_CAST
@objc func setUpCastController(_ reactTag: NSNumber) {
self.bridge.uiManager.addUIBlock { uiManager, viewRegistry in
Expand Down