UNPKG

@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
"use strict"; 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", }, });