@interactify-live/player-react-native
Version:
React Native library for Interactify player with media display, widgets, and MQTT integration
307 lines (306 loc) • 13.8 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.InteractifyPlayer = void 0;
const jsx_runtime_1 = require("react/jsx-runtime");
const react_1 = require("react");
const react_native_1 = require("react-native");
const Spinner_1 = __importDefault(require("./Spinner"));
const MediaDisplayNative_1 = require("../MediaDisplayNative");
const InteractifyConnector_1 = require("../InteractifyConnector");
exports.InteractifyPlayer = (0, react_1.forwardRef)(({ media, interactions = [], autoPlay = false, muted = false, loop = false, isDraggable = true, onPlay, onPause, onEnded, onTimeUpdate: _onTimeUpdate, onError, onInteractionClick, onInteractionDragEnd, onVideoReady, loadingIndicator, errorIndicator, className: _className, style, options, onStatusChange, onNewMessageReceived, }, ref) => {
const isDarkMode = (0, react_native_1.useColorScheme)() === "dark";
const mediaDisplayRef = (0, react_1.useRef)(null);
const videoElementRef = (0, react_1.useRef)(null);
// Player state
const [playerState, setPlayerState] = (0, react_1.useState)({
isPlaying: false,
isLoaded: false,
error: null,
buffering: false,
connectionStatus: "OFFLINE",
messages: [],
currentTime: 0,
duration: 0,
isMuted: muted,
});
// MQTT Connector
const [connector] = (0, react_1.useState)(() => new InteractifyConnector_1.Connector());
// Initialize MQTT connection
(0, react_1.useEffect)(() => {
if (!options)
return;
// Subscribe to connection status
const statusUnsubscribe = connector.status.subscribe((status) => {
console.log("Player: Connection status:", status);
setPlayerState((prev) => ({ ...prev, connectionStatus: status }));
onStatusChange?.(status);
});
// Subscribe to messages
const messagesUnsubscribe = connector.messages.subscribe((newMessages) => {
console.log("Player: New messages:", newMessages);
setPlayerState((prev) => ({ ...prev, messages: newMessages }));
// Call onNewMessageReceived for each new message
newMessages.forEach((message) => {
onNewMessageReceived?.(message);
});
});
// Connect to MQTT broker
connector.connect({
session: options.session,
space_slug: options.space_slug,
token: options.token,
user_id: options.user_id,
scope: options.scope,
slug: options.slug,
brokerUrl: options.brokerUrl,
});
// Cleanup on unmount
return () => {
statusUnsubscribe();
messagesUnsubscribe();
connector.disconnect();
};
}, [connector, options, onStatusChange, onNewMessageReceived]);
// Media event handlers
const handleLoadStart = (0, react_1.useCallback)(() => {
console.log("Player: Load start");
setPlayerState((prev) => ({ ...prev, error: null, buffering: true }));
}, []);
const handleLoad = (0, react_1.useCallback)(() => {
console.log("Player: Load success");
setPlayerState((prev) => ({
...prev,
isLoaded: true,
error: null,
buffering: false,
}));
}, []);
const handleError = (0, react_1.useCallback)((media, error) => {
console.error("Player: Load error:", error);
setPlayerState((prev) => ({
...prev,
error: error.message,
isLoaded: false,
buffering: false,
}));
onError?.(error);
}, [onError]);
const handlePlay = (0, react_1.useCallback)(() => {
console.log("Player: Play event");
setPlayerState((prev) => ({ ...prev, isPlaying: true }));
onPlay?.();
}, [onPlay]);
const handlePause = (0, react_1.useCallback)(() => {
console.log("Player: Pause event");
setPlayerState((prev) => ({ ...prev, isPlaying: false }));
onPause?.();
}, [onPause]);
const handleEnded = (0, react_1.useCallback)(() => {
console.log("Player: Ended event", "loop:", loop);
// Only set playing to false if not looping
if (!loop) {
console.log("Player: Setting isPlaying to false (not looping)");
setPlayerState((prev) => ({ ...prev, isPlaying: false }));
}
else {
console.log("Player: Keeping isPlaying true (looping enabled)");
}
onEnded?.();
}, [onEnded, loop]);
// Note: handleTimeUpdate is available but not currently used in MediaDisplay events
// It can be used when MediaDisplay supports time update events
const handleVideoReady = (0, react_1.useCallback)((videoRef) => {
console.log("Player: Video ready");
videoElementRef.current = videoRef;
onVideoReady?.(videoRef);
}, [onVideoReady]);
// Interaction handlers
const handleInteractionPress = (0, react_1.useCallback)((interaction) => {
console.log("Player: Interaction pressed:", interaction);
onInteractionClick?.(interaction);
}, [onInteractionClick]);
const handleInteractionDragEnd = (0, react_1.useCallback)((index, position) => {
console.log("InteractifyPlayer: handleInteractionDragEnd", {
index,
position,
});
console.log("InteractifyPlayer: isDraggable prop:", isDraggable);
console.log("InteractifyPlayer: onInteractionDragEnd callback exists:", !!onInteractionDragEnd);
onInteractionDragEnd?.(index, position);
}, [onInteractionDragEnd, isDraggable]);
// Expose methods via ref
(0, react_1.useImperativeHandle)(ref, () => ({
play: async () => {
try {
if (mediaDisplayRef.current) {
await mediaDisplayRef.current.play();
setPlayerState((prev) => ({ ...prev, isPlaying: true }));
}
}
catch (error) {
console.error("Player: Failed to play:", error);
}
},
pause: () => {
if (mediaDisplayRef.current) {
mediaDisplayRef.current.pause();
setPlayerState((prev) => ({ ...prev, isPlaying: false }));
}
},
mute: () => {
if (mediaDisplayRef.current) {
mediaDisplayRef.current.mute();
setPlayerState((prev) => ({ ...prev, isMuted: true }));
}
},
unmute: () => {
if (mediaDisplayRef.current) {
mediaDisplayRef.current.unmute();
setPlayerState((prev) => ({ ...prev, isMuted: false }));
}
},
getCurrentTime: () => playerState.currentTime,
setCurrentTime: (time) => {
if (mediaDisplayRef.current) {
mediaDisplayRef.current.seek(time);
}
},
isMuted: () => playerState.isMuted,
isPlaying: () => playerState.isPlaying,
setInteractions: (newInteractions) => {
// This would need to be implemented with state management
console.log("Setting interactions:", newInteractions);
},
getVideoElement: () => videoElementRef.current,
loadStream: (stream) => {
if (mediaDisplayRef.current) {
// Note: loadStream method may not be available on MediaDisplayInstance
console.log("loadStream called with:", stream);
}
},
publishEvent: (type) => {
try {
connector.publish(type);
console.log("Player: Event published:", type);
}
catch (error) {
console.error("Player: Failed to publish event:", error);
}
},
sendMessage: (message) => {
try {
connector.sendMessage(message);
console.log("Player: Message sent");
}
catch (error) {
console.error("Player: Failed to send message:", error);
}
},
subscribeToNewMessages: (callback) => {
return connector.messages.subscribe((messages) => {
messages.forEach(callback);
});
},
}));
return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [styles.container, style], children: [(0, jsx_runtime_1.jsx)(react_native_1.StatusBar, { barStyle: isDarkMode ? "light-content" : "dark-content" }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.mediaContainer, children: (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.videoOverlayContainer, children: [(() => {
console.log("Player: Rendering MediaDisplay with loop:", loop, "autoPlay:", autoPlay);
return null;
})(), (0, jsx_runtime_1.jsx)(MediaDisplayNative_1.MediaDisplay, { ref: mediaDisplayRef, media: media, width: 400, height: 800, autoplay: autoPlay, muted: muted, loop: loop, objectFit: "contain", backgroundColor: "#000000", onLoadStart: handleLoadStart, onLoad: handleLoad, onError: handleError, onPlay: handlePlay, onPause: handlePause, onEnded: handleEnded, onVideoReady: handleVideoReady }), interactions && interactions.length > 0 && ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.interactionsOverlay, children: interactions.map((interaction, index) => {
if (interaction.interaction === "text") {
return ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
styles.interactionText,
{
left: `${interaction.geometric.x}%`,
top: `${interaction.geometric.y}%`,
width: interaction.geometric.width,
height: interaction.geometric.height,
},
], pointerEvents: "box-none", children: (0, jsx_runtime_1.jsx)(react_native_1.TouchableOpacity, { style: [
styles.interactionTextContent,
{
backgroundColor: interaction.payload.background ||
"rgba(0,0,0,0.5)",
borderRadius: interaction.payload.borderRadius !== undefined
? interaction.payload.borderRadius
: 8,
},
], onPress: () => handleInteractionPress(interaction), activeOpacity: 0.7, children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: {
fontSize: interaction.payload.size || 16,
color: interaction.payload.color || "#ffffff",
fontWeight: "bold",
}, children: interaction.payload.text }) }) }, `interaction-${index}`));
}
return null;
}) }))] }) }), playerState.buffering &&
(loadingIndicator || ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.bufferingContainer, children: (0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.spinnerBackground, children: (0, jsx_runtime_1.jsx)(Spinner_1.default, { size: 40, color: "white" }) }) })))] }));
});
const styles = react_native_1.StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#f5f5f5",
},
mediaContainer: {
flex: 1,
width: "100%",
height: "100%",
},
videoOverlayContainer: {
position: "relative",
width: "100%",
height: "100%",
backgroundColor: "#000000",
overflow: "hidden",
},
interactionsOverlay: {
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
zIndex: 10,
pointerEvents: "box-none",
},
interactionText: {
position: "absolute",
justifyContent: "center",
alignItems: "center",
pointerEvents: "none",
},
interactionTextContent: {
padding: 8,
textAlign: "center",
fontWeight: "bold",
},
errorContainer: {
padding: 15,
backgroundColor: "#ffebee",
marginHorizontal: 20,
borderRadius: 8,
borderWidth: 1,
borderColor: "#f44336",
},
errorText: {
color: "#d32f2f",
fontSize: 14,
fontWeight: "600",
},
bufferingContainer: {
position: "absolute",
top: "50%",
left: "50%",
transform: [{ translateX: -50 }, { translateY: -50 }],
justifyContent: "center",
alignItems: "center",
zIndex: 2,
},
spinnerBackground: {
backgroundColor: "rgba(0, 0, 0, 0.7)",
borderRadius: 20,
padding: 20,
justifyContent: "center",
alignItems: "center",
},
});