# Streamlit FunPlayer
A comprehensive media player component for synchronized audio/video and haptic playback using funscripts and Buttplug.io compatible devices.
## โจ Features
### ๐ฅ Universal Media Support
- **Video playback**: Standard 2D video formats (MP4, WebM, MOV, AVI)
- **VR video support**: 360ยฐ/180ยฐ spherical video with A-Frame integration
- **Audio playback**: MP3, WAV, OGG, M4A, AAC formats
- **Timeline-only mode**: Haptic-only playback without media (generates silent audio)
- **Playlist support**: Multiple items with automatic progression and manual navigation
### ๐ฎ Advanced Haptic Integration
- **Buttplug.io ecosystem**: Full compatibility with Intiface Central and 100+ supported devices
- **Multi-channel funscripts**: Support for complex scripts with multiple actuator channels (pos, vibrate, rotate, linear, etc.)
- **Intelligent channel mapping**: Automatic detection and mapping of funscript channels to device actuators
- **Per-channel configuration**: Individual scale, time offset, range, and invert settings for each channel
- **High-frequency updates**: Configurable refresh rates from 30Hz to 200Hz for smooth haptic feedback
- **Real-time interpolation**: Smooth value transitions between funscript keyframes
### โ๏ธ Professional Configuration
- **Device management**: Automatic scanning, connection, and capability detection
- **Advanced timing controls**: Global and per-channel time offsets for perfect synchronization
- **Scaling and range control**: Fine-tune intensity and output ranges per actuator
- **Multiple actuator types**: Support for vibration, linear motion, rotation, and oscillation
- **Virtual mode**: Test and develop without physical devices
### ๐ Visual Feedback
- **Real-time haptic visualizer**: Live waveform display with gaussian interpolation
- **Multi-actuator visualization**: Color-coded display for multiple simultaneous channels
- **Performance monitoring**: Update rate and timing statistics
- **Debug information**: Comprehensive state inspection and troubleshooting tools
### ๐จ Streamlit Integration
- **Automatic theming**: Seamless integration with Streamlit's light/dark themes
- **Responsive design**: Adapts to Streamlit's layout system
- **Custom themes**: Override colors, fonts, and styling via Python
- **Component lifecycle**: Proper cleanup and resource management
## ๐ Quick Start
### Prerequisites
1. **Install Intiface Central**
```bash
# Download from https://intiface.com/central/
# Start the WebSocket server (default: ws://localhost:12345)
```
2. **Install the component**
```bash
pip install streamlit-funplayer
```
### Basic Usage
```python
import streamlit as st
from streamlit_funplayer import funplayer
st.title("๐ฎ FunPlayer Demo")
# Simple video + haptic sync
funplayer(
playlist=[{
'media': 'https://example.com/video.mp4',
'funscript': 'https://example.com/script.funscript',
'title': 'Demo Scene'
}]
)
```
### Multiple Content Types
```python
# Audio + haptics
funplayer(
playlist=[{
'media': 'audio.mp3',
'funscript': funscript_data,
'title': 'Audio Experience'
}]
)
# Haptic-only (no media)
funplayer(
playlist=[{
'funscript': 'script.funscript',
'duration': 120, # 2 minutes
'title': 'Pure Haptic'
}]
)
# Mixed playlist
funplayer(
playlist=[
{
'media': 'video1.mp4',
'funscript': 'script1.funscript',
'title': 'Scene 1'
},
{
'media': 'audio2.mp3',
'funscript': script_data,
'title': 'Scene 2'
},
{
'funscript': 'haptic_only.funscript',
'duration': 60,
'title': 'Haptic Only'
}
]
)
```
### Working with Funscripts
```python
import json
from streamlit_funplayer import funplayer, load_funscript, create_funscript
# Load from file
funscript_data = load_funscript("my_script.funscript")
# Create programmatically
actions = [
{"at": 0, "pos": 0},
{"at": 1000, "pos": 100},
{"at": 2000, "pos": 50},
{"at": 3000, "pos": 0}
]
funscript = {
"actions":actions, # required
#optional metadata
"range":100,
"version":1,
"title": "Generated Script",
"duration":3000
}
funplayer(playlist=[{
'media': 'audio.mp3',
'funscript': funscript,
'poster':'thumbnail.jpg'
'title': 'Simple funscript'
}])
# Multi-channel funscript
multi_channel = {
"version": "1.0",
"linear": [
{"at": 0, "pos": 0},
{"at": 1000, "pos": 100}
],
"vibrate": [
{"at": 0, "v": 0.0},
{"at": 1000, "v": 1.0}
],
"rotate": [
{"at": 0, "speed": 0.2, "clockwise": True},
{"at": 1000, "speed": 0.5, "clockwise": False}
]
}
funplayer(playlist=[{
'media': 'video.mp4',
'funscript': multi_channel,
'title': 'Multi-Channel Experience'
}])
```
### File Upload Interface
```python
import streamlit as st
import json
import base64
from streamlit_funplayer import funplayer
def file_to_data_url(uploaded_file):
"""Convert uploaded file to data URL for browser compatibility"""
if not uploaded_file:
return None
content = uploaded_file.getvalue()
extension = uploaded_file.name.split('.')[-1].lower()
mime_types = {
'mp4': 'video/mp4', 'webm': 'video/webm',
'mp3': 'audio/mpeg', 'wav': 'audio/wav'
}
mime_type = mime_types.get(extension, 'application/octet-stream')
b64_content = base64.b64encode(content).decode('utf-8')
return f"data:{mime_type};base64,{b64_content}"
# UI
st.title("๐ฎ Upload & Play")
media_file = st.file_uploader(
"Media File",
type=['mp4', 'webm', 'mp3', 'wav']
)
funscript_file = st.file_uploader(
"Funscript File",
type=['funscript', 'json']
)
if media_file or funscript_file:
playlist_item = {}
if media_file:
playlist_item['media'] = file_to_data_url(media_file)
playlist_item['title'] = media_file.name
if funscript_file:
playlist_item['funscript'] = json.loads(
funscript_file.getvalue().decode('utf-8')
)
funplayer(playlist=[playlist_item])
```
### Custom Themes
```python
# Dark theme
dark_theme = {
'primaryColor': '#FF6B6B',
'backgroundColor': '#1E1E1E',
'secondaryBackgroundColor': '#2D2D2D',
'textColor': '#FFFFFF',
'borderColor': '#404040'
}
funplayer(
playlist=[{
'media': 'video.mp4',
'funscript': 'script.funscript'
}],
theme=dark_theme
)
```
## ๐ง React Component Architecture
**streamlit-funplayer** is fundamentally a **standalone React component** that can work in any React application. The Streamlit wrapper is simply a convenience layer for Python integration.
### Core Architecture
```
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ FunPlayer (React) โ
โ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ โ
โ โ MediaPlayer โ โ HapticSettings โ โ Visualizer โ โ
โ โ (Video.js) โ โ โ โ (Canvas) โ โ
โ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ โ
โ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ โ
โ โ ButtPlugManager โ โFunscriptManager โ โ MediaManager โ โ
โ โ (buttplug.js) โ โ (interpolation)โ โ (utilities) โ โ
โ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ StreamlitFunPlayer (Wrapper) โ
โ โข Theme integration โ
โ โข Props conversion โ
โ โข Streamlit lifecycle โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
```
### Key Technical Components
#### ButtPlugManager
```javascript
// Abstraction layer over buttplug.js
const manager = new ButtPlugManager();
await manager.connect('ws://localhost:12345');
await manager.scan(5000);
manager.selectDevice(0);
await manager.vibrate(0.8, actuatorIndex);
```
#### FunscriptManager
```javascript
// Funscript parsing and interpolation
const fsManager = new FunscriptManager();
fsManager.load(funscriptData);
fsManager.autoMapChannels(deviceCapabilities);
const value = fsManager.interpolateAt(currentTime, 'pos');
```
#### MediaPlayer (Video.js)
```javascript
// Unified media interface with playlist support
<MediaPlayer
playlist={processedPlaylist}
onPlay={handlePlay}
onTimeUpdate={handleTimeUpdate}
onPlaylistItemChange={handleItemChange}
/>
```
#### HapticVisualizer
```javascript
// Real-time gaussian waveform visualization
<HapticVisualizerComponent
getCurrentActuatorData={() => actuatorDataMap}
isPlaying={isPlaying}
/>
```
### React Integration
To use FunPlayer directly in a React app:
```javascript
import FunPlayer from './FunPlayer';
function App() {
const playlist = [
{
media: 'video.mp4',
funscript: funscriptData,
title: 'Scene 1'
}
];
return (
<div className="app">
<FunPlayer
playlist={playlist}
onResize={() => console.log('Player resized')}
/>
</div>
);
}
```
### Data Flow
1. **Playlist Processing**: MediaManager converts playlist items to Video.js format
2. **Media Events**: Video.js fires play/pause/timeupdate events
3. **Haptic Loop**: 60Hz interpolation loop syncs with media time
4. **Device Commands**: ButtPlugManager sends commands to physical devices
5. **Visualization**: Real-time canvas rendering of actuator states
### Advanced Features
#### Custom Funscript Formats
```javascript
// Supports flexible funscript schemas
{
"actions": [...], // Standard position channel
"vibrate": [...], // Vibration channel
"rotate": [...], // Rotation channel
"customChannel": [...], // Any named channel
"metadata": {
"channels": {
"customChannel": {
"type": "linear",
"actuator": 0
}
}
}
}
```
#### Performance Optimization
- **Interpolation caching**: Efficient seeking and time progression
- **Throttled commands**: Prevents device command flooding
- **Memory management**: Automatic cleanup of media resources
- **Playlist transitions**: Seamless switching between items
#### Device Abstraction
```javascript
// Works with or without physical devices
const capabilities = buttplugManager.getCapabilities();
// โ { actuators: [{vibrate: true, linear: false, ...}], counts: {...} }
// Virtual mode for development
funscriptManager.autoMapChannels(null); // Maps to virtual actuators
```
## ๐ API Reference
### funplayer()
```python
funplayer(
playlist=None, # List of playlist items
theme=None, # Custom theme dictionary
key=None # Streamlit component key
)
```
### Playlist Item Format
```python
{
'media': str, # URL/path to media file (optional)
'funscript': dict|str, # Funscript data or URL (optional)
'poster': str, # Poster image URL (optional)
'title': str, # Display title (optional)
'duration': float, # Duration in seconds (for haptic-only)
'media_type': str, # Force media type detection
'media_info': str # Additional metadata
}
```
### Utility Functions
```python
# Load funscript from file
load_funscript(file_path: str) -> dict
# Create funscript programmatically
create_funscript(
actions: list, # [{"at": time_ms, "pos": 0-100}, ...]
metadata: dict = None # Optional metadata
) -> dict
```
## ๐ฏ Use Cases
- **Adult content platforms**: Synchronized interactive experiences
- **VR applications**: Immersive haptic feedback in virtual environments
- **Audio experiences**: Music/podcast enhancement with haptic rhythm
- **Accessibility tools**: Haptic feedback for hearing-impaired users
- **Research platforms**: Haptic interaction studies and experiments
- **Gaming**: Rhythm games and interactive experiences
## ๐ง Development
### Frontend Development
```bash
cd streamlit_funplayer/frontend
npm install
npm start # Runs on localhost:3001
```
### Testing with Streamlit
```bash
# In project root
streamlit run funplayer.py
```
### Building for Production
```bash
cd frontend
npm run build
pip install -e . # Install with built frontend
```
## โ ๏ธ Requirements
- **Python 3.9+**
- **Streamlit 1.45+**
- **Intiface Central** (for device connectivity)
- **Modern browser** with WebSocket support
- **HTTPS connection** (required for device access in production)
## ๐ค Contributing
1. Fork the repository
2. Create a feature branch
3. Test with real devices when possible
4. Ensure Streamlit theme compatibility
5. Submit a pull request
## ๐ License
MIT License - see LICENSE file for details.
## ๐ Acknowledgments
- [Buttplug.io](https://buttplug.io) - Device communication protocol
- [Intiface](https://intiface.com) - Desktop bridge application
- [Video.js](https://videojs.com) - Media player framework
- [Streamlit](https://streamlit.io) - Python web app framework
- The funscript community for haptic scripting standards
Raw data
{
"_id": null,
"home_page": "https://github.com/B4PT0R/streamlit-funplayer",
"name": "streamlit-funplayer",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.9",
"maintainer_email": "Baptiste Ferrand <bferrand.math@gmail.com>",
"keywords": "streamlit, component, media-player, haptic-feedback, funscript, buttplug, adult-tech, interactive-media, vr, react",
"author": "Baptiste Ferrand",
"author_email": "Baptiste Ferrand <bferrand.math@gmail.com>",
"download_url": "https://files.pythonhosted.org/packages/9b/c6/6050c8e831ad0f4d9e6dbabcc532152751a409fe0b662aea74a0d20c092f/streamlit_funplayer-0.1.0.tar.gz",
"platform": null,
"description": "# Streamlit FunPlayer\n\nA comprehensive media player component for synchronized audio/video and haptic playback using funscripts and Buttplug.io compatible devices.\n\n## \u2728 Features\n\n### \ud83c\udfa5 Universal Media Support\n- **Video playback**: Standard 2D video formats (MP4, WebM, MOV, AVI)\n- **VR video support**: 360\u00b0/180\u00b0 spherical video with A-Frame integration\n- **Audio playback**: MP3, WAV, OGG, M4A, AAC formats\n- **Timeline-only mode**: Haptic-only playback without media (generates silent audio)\n- **Playlist support**: Multiple items with automatic progression and manual navigation\n\n### \ud83c\udfae Advanced Haptic Integration\n- **Buttplug.io ecosystem**: Full compatibility with Intiface Central and 100+ supported devices\n- **Multi-channel funscripts**: Support for complex scripts with multiple actuator channels (pos, vibrate, rotate, linear, etc.)\n- **Intelligent channel mapping**: Automatic detection and mapping of funscript channels to device actuators\n- **Per-channel configuration**: Individual scale, time offset, range, and invert settings for each channel\n- **High-frequency updates**: Configurable refresh rates from 30Hz to 200Hz for smooth haptic feedback\n- **Real-time interpolation**: Smooth value transitions between funscript keyframes\n\n### \u2699\ufe0f Professional Configuration\n- **Device management**: Automatic scanning, connection, and capability detection\n- **Advanced timing controls**: Global and per-channel time offsets for perfect synchronization\n- **Scaling and range control**: Fine-tune intensity and output ranges per actuator\n- **Multiple actuator types**: Support for vibration, linear motion, rotation, and oscillation\n- **Virtual mode**: Test and develop without physical devices\n\n### \ud83d\udcca Visual Feedback\n- **Real-time haptic visualizer**: Live waveform display with gaussian interpolation\n- **Multi-actuator visualization**: Color-coded display for multiple simultaneous channels\n- **Performance monitoring**: Update rate and timing statistics\n- **Debug information**: Comprehensive state inspection and troubleshooting tools\n\n### \ud83c\udfa8 Streamlit Integration\n- **Automatic theming**: Seamless integration with Streamlit's light/dark themes\n- **Responsive design**: Adapts to Streamlit's layout system\n- **Custom themes**: Override colors, fonts, and styling via Python\n- **Component lifecycle**: Proper cleanup and resource management\n\n## \ud83d\ude80 Quick Start\n\n### Prerequisites\n\n1. **Install Intiface Central**\n ```bash\n # Download from https://intiface.com/central/\n # Start the WebSocket server (default: ws://localhost:12345)\n ```\n\n2. **Install the component**\n ```bash\n pip install streamlit-funplayer\n ```\n\n### Basic Usage\n\n```python\nimport streamlit as st\nfrom streamlit_funplayer import funplayer\n\nst.title(\"\ud83c\udfae FunPlayer Demo\")\n\n# Simple video + haptic sync\nfunplayer(\n playlist=[{\n 'media': 'https://example.com/video.mp4',\n 'funscript': 'https://example.com/script.funscript',\n 'title': 'Demo Scene'\n }]\n)\n```\n\n### Multiple Content Types\n\n```python\n# Audio + haptics\nfunplayer(\n playlist=[{\n 'media': 'audio.mp3',\n 'funscript': funscript_data,\n 'title': 'Audio Experience'\n }]\n)\n\n# Haptic-only (no media)\nfunplayer(\n playlist=[{\n 'funscript': 'script.funscript',\n 'duration': 120, # 2 minutes\n 'title': 'Pure Haptic'\n }]\n)\n\n# Mixed playlist\nfunplayer(\n playlist=[\n {\n 'media': 'video1.mp4',\n 'funscript': 'script1.funscript',\n 'title': 'Scene 1'\n },\n {\n 'media': 'audio2.mp3', \n 'funscript': script_data,\n 'title': 'Scene 2'\n },\n {\n 'funscript': 'haptic_only.funscript',\n 'duration': 60,\n 'title': 'Haptic Only'\n }\n ]\n)\n```\n\n### Working with Funscripts\n\n```python\nimport json\nfrom streamlit_funplayer import funplayer, load_funscript, create_funscript\n\n# Load from file\nfunscript_data = load_funscript(\"my_script.funscript\")\n\n# Create programmatically\nactions = [\n {\"at\": 0, \"pos\": 0},\n {\"at\": 1000, \"pos\": 100},\n {\"at\": 2000, \"pos\": 50},\n {\"at\": 3000, \"pos\": 0}\n]\n\nfunscript = {\n \"actions\":actions, # required\n\n #optional metadata\n \"range\":100,\n \"version\":1,\n \"title\": \"Generated Script\",\n \"duration\":3000\n}\n\nfunplayer(playlist=[{\n 'media': 'audio.mp3',\n 'funscript': funscript,\n 'poster':'thumbnail.jpg'\n 'title': 'Simple funscript'\n}])\n\n# Multi-channel funscript\nmulti_channel = {\n \"version\": \"1.0\",\n \"linear\": [\n {\"at\": 0, \"pos\": 0},\n {\"at\": 1000, \"pos\": 100}\n ],\n \"vibrate\": [\n {\"at\": 0, \"v\": 0.0},\n {\"at\": 1000, \"v\": 1.0}\n ],\n \"rotate\": [\n {\"at\": 0, \"speed\": 0.2, \"clockwise\": True},\n {\"at\": 1000, \"speed\": 0.5, \"clockwise\": False}\n ]\n}\n\nfunplayer(playlist=[{\n 'media': 'video.mp4',\n 'funscript': multi_channel,\n 'title': 'Multi-Channel Experience'\n}])\n```\n\n### File Upload Interface\n\n```python\nimport streamlit as st\nimport json\nimport base64\nfrom streamlit_funplayer import funplayer\n\ndef file_to_data_url(uploaded_file):\n \"\"\"Convert uploaded file to data URL for browser compatibility\"\"\"\n if not uploaded_file:\n return None\n \n content = uploaded_file.getvalue()\n extension = uploaded_file.name.split('.')[-1].lower()\n \n mime_types = {\n 'mp4': 'video/mp4', 'webm': 'video/webm',\n 'mp3': 'audio/mpeg', 'wav': 'audio/wav'\n }\n \n mime_type = mime_types.get(extension, 'application/octet-stream')\n b64_content = base64.b64encode(content).decode('utf-8')\n return f\"data:{mime_type};base64,{b64_content}\"\n\n# UI\nst.title(\"\ud83c\udfae Upload & Play\")\n\nmedia_file = st.file_uploader(\n \"Media File\", \n type=['mp4', 'webm', 'mp3', 'wav']\n)\n\nfunscript_file = st.file_uploader(\n \"Funscript File\", \n type=['funscript', 'json']\n)\n\nif media_file or funscript_file:\n playlist_item = {}\n \n if media_file:\n playlist_item['media'] = file_to_data_url(media_file)\n playlist_item['title'] = media_file.name\n \n if funscript_file:\n playlist_item['funscript'] = json.loads(\n funscript_file.getvalue().decode('utf-8')\n )\n \n funplayer(playlist=[playlist_item])\n```\n\n### Custom Themes\n\n```python\n# Dark theme\ndark_theme = {\n 'primaryColor': '#FF6B6B',\n 'backgroundColor': '#1E1E1E',\n 'secondaryBackgroundColor': '#2D2D2D',\n 'textColor': '#FFFFFF',\n 'borderColor': '#404040'\n}\n\nfunplayer(\n playlist=[{\n 'media': 'video.mp4',\n 'funscript': 'script.funscript'\n }],\n theme=dark_theme\n)\n```\n\n## \ud83d\udd27 React Component Architecture\n\n**streamlit-funplayer** is fundamentally a **standalone React component** that can work in any React application. The Streamlit wrapper is simply a convenience layer for Python integration.\n\n### Core Architecture\n\n```\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 FunPlayer (React) \u2502\n\u2502 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502\n\u2502 \u2502 MediaPlayer \u2502 \u2502 HapticSettings \u2502 \u2502 Visualizer \u2502 \u2502\n\u2502 \u2502 (Video.js) \u2502 \u2502 \u2502 \u2502 (Canvas) \u2502 \u2502\n\u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502\n\u2502 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502\n\u2502 \u2502 ButtPlugManager \u2502 \u2502FunscriptManager \u2502 \u2502 MediaManager \u2502 \u2502\n\u2502 \u2502 (buttplug.js) \u2502 \u2502 (interpolation)\u2502 \u2502 (utilities) \u2502 \u2502\n\u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n \u2502\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 StreamlitFunPlayer (Wrapper) \u2502\n\u2502 \u2022 Theme integration \u2502\n\u2502 \u2022 Props conversion \u2502\n\u2502 \u2022 Streamlit lifecycle \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n```\n\n### Key Technical Components\n\n#### ButtPlugManager\n```javascript\n// Abstraction layer over buttplug.js\nconst manager = new ButtPlugManager();\nawait manager.connect('ws://localhost:12345');\nawait manager.scan(5000);\nmanager.selectDevice(0);\nawait manager.vibrate(0.8, actuatorIndex);\n```\n\n#### FunscriptManager \n```javascript\n// Funscript parsing and interpolation\nconst fsManager = new FunscriptManager();\nfsManager.load(funscriptData);\nfsManager.autoMapChannels(deviceCapabilities);\nconst value = fsManager.interpolateAt(currentTime, 'pos');\n```\n\n#### MediaPlayer (Video.js)\n```javascript\n// Unified media interface with playlist support\n<MediaPlayer\n playlist={processedPlaylist}\n onPlay={handlePlay}\n onTimeUpdate={handleTimeUpdate}\n onPlaylistItemChange={handleItemChange}\n/>\n```\n\n#### HapticVisualizer\n```javascript\n// Real-time gaussian waveform visualization\n<HapticVisualizerComponent\n getCurrentActuatorData={() => actuatorDataMap}\n isPlaying={isPlaying}\n/>\n```\n\n### React Integration\n\nTo use FunPlayer directly in a React app:\n\n```javascript\nimport FunPlayer from './FunPlayer';\n\nfunction App() {\n const playlist = [\n {\n media: 'video.mp4',\n funscript: funscriptData,\n title: 'Scene 1'\n }\n ];\n\n return (\n <div className=\"app\">\n <FunPlayer \n playlist={playlist}\n onResize={() => console.log('Player resized')}\n />\n </div>\n );\n}\n```\n\n### Data Flow\n\n1. **Playlist Processing**: MediaManager converts playlist items to Video.js format\n2. **Media Events**: Video.js fires play/pause/timeupdate events \n3. **Haptic Loop**: 60Hz interpolation loop syncs with media time\n4. **Device Commands**: ButtPlugManager sends commands to physical devices\n5. **Visualization**: Real-time canvas rendering of actuator states\n\n### Advanced Features\n\n#### Custom Funscript Formats\n```javascript\n// Supports flexible funscript schemas\n{\n \"actions\": [...], // Standard position channel\n \"vibrate\": [...], // Vibration channel \n \"rotate\": [...], // Rotation channel\n \"customChannel\": [...], // Any named channel\n \"metadata\": {\n \"channels\": {\n \"customChannel\": {\n \"type\": \"linear\",\n \"actuator\": 0\n }\n }\n }\n}\n```\n\n#### Performance Optimization\n- **Interpolation caching**: Efficient seeking and time progression\n- **Throttled commands**: Prevents device command flooding\n- **Memory management**: Automatic cleanup of media resources\n- **Playlist transitions**: Seamless switching between items\n\n#### Device Abstraction\n```javascript\n// Works with or without physical devices\nconst capabilities = buttplugManager.getCapabilities();\n// \u2192 { actuators: [{vibrate: true, linear: false, ...}], counts: {...} }\n\n// Virtual mode for development\nfunscriptManager.autoMapChannels(null); // Maps to virtual actuators\n```\n\n## \ud83d\udccb API Reference\n\n### funplayer()\n\n```python\nfunplayer(\n playlist=None, # List of playlist items\n theme=None, # Custom theme dictionary \n key=None # Streamlit component key\n)\n```\n\n### Playlist Item Format\n\n```python\n{\n 'media': str, # URL/path to media file (optional)\n 'funscript': dict|str, # Funscript data or URL (optional) \n 'poster': str, # Poster image URL (optional)\n 'title': str, # Display title (optional)\n 'duration': float, # Duration in seconds (for haptic-only)\n 'media_type': str, # Force media type detection\n 'media_info': str # Additional metadata\n}\n```\n\n### Utility Functions\n\n```python\n# Load funscript from file\nload_funscript(file_path: str) -> dict\n\n# Create funscript programmatically\ncreate_funscript(\n actions: list, # [{\"at\": time_ms, \"pos\": 0-100}, ...]\n metadata: dict = None # Optional metadata\n) -> dict\n```\n\n## \ud83c\udfaf Use Cases\n\n- **Adult content platforms**: Synchronized interactive experiences\n- **VR applications**: Immersive haptic feedback in virtual environments \n- **Audio experiences**: Music/podcast enhancement with haptic rhythm\n- **Accessibility tools**: Haptic feedback for hearing-impaired users\n- **Research platforms**: Haptic interaction studies and experiments\n- **Gaming**: Rhythm games and interactive experiences\n\n## \ud83d\udd27 Development\n\n### Frontend Development\n```bash\ncd streamlit_funplayer/frontend\nnpm install\nnpm start # Runs on localhost:3001\n```\n\n### Testing with Streamlit\n```bash\n# In project root\nstreamlit run funplayer.py\n```\n\n### Building for Production\n```bash\ncd frontend \nnpm run build\npip install -e . # Install with built frontend\n```\n\n## \u26a0\ufe0f Requirements\n\n- **Python 3.9+**\n- **Streamlit 1.45+** \n- **Intiface Central** (for device connectivity)\n- **Modern browser** with WebSocket support\n- **HTTPS connection** (required for device access in production)\n\n## \ud83e\udd1d Contributing\n\n1. Fork the repository\n2. Create a feature branch\n3. Test with real devices when possible\n4. Ensure Streamlit theme compatibility\n5. Submit a pull request\n\n## \ud83d\udcc4 License\n\nMIT License - see LICENSE file for details.\n\n## \ud83d\ude4f Acknowledgments\n\n- [Buttplug.io](https://buttplug.io) - Device communication protocol\n- [Intiface](https://intiface.com) - Desktop bridge application \n- [Video.js](https://videojs.com) - Media player framework\n- [Streamlit](https://streamlit.io) - Python web app framework\n- The funscript community for haptic scripting standards\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "Streamlit component for synchronized media and haptic playback using funscripts and Buttplug.io compatible devices",
"version": "0.1.0",
"project_urls": {
"Bug Tracker": "https://github.com/B4PT0R/streamlit-funplayer/issues",
"Changelog": "https://github.com/B4PT0R/streamlit-funplayer/releases",
"Documentation": "https://github.com/B4PT0R/streamlit-funplayer#readme",
"Homepage": "https://github.com/B4PT0R/streamlit-funplayer",
"Repository": "https://github.com/B4PT0R/streamlit-funplayer"
},
"split_keywords": [
"streamlit",
" component",
" media-player",
" haptic-feedback",
" funscript",
" buttplug",
" adult-tech",
" interactive-media",
" vr",
" react"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "01a0c62ace9929ef43292e78a37d795eb6464eb617f7935d8043c063d58afb7f",
"md5": "88b298d4d4478761fb2c9cf86ed52a61",
"sha256": "88f41a94c30fca8ff957bac9bba2b0b8b71b15e693f59928c3016329c0e380a5"
},
"downloads": -1,
"filename": "streamlit_funplayer-0.1.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "88b298d4d4478761fb2c9cf86ed52a61",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.9",
"size": 1010754,
"upload_time": "2025-06-21T05:51:37",
"upload_time_iso_8601": "2025-06-21T05:51:37.201443Z",
"url": "https://files.pythonhosted.org/packages/01/a0/c62ace9929ef43292e78a37d795eb6464eb617f7935d8043c063d58afb7f/streamlit_funplayer-0.1.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "9bc66050c8e831ad0f4d9e6dbabcc532152751a409fe0b662aea74a0d20c092f",
"md5": "2b6b41b8c4ab85a907a021fb5932272a",
"sha256": "1432b178c5e897556f111ce2949bf7d7e258c5eaa52e1262591c00af62d28388"
},
"downloads": -1,
"filename": "streamlit_funplayer-0.1.0.tar.gz",
"has_sig": false,
"md5_digest": "2b6b41b8c4ab85a907a021fb5932272a",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.9",
"size": 1013711,
"upload_time": "2025-06-21T05:51:39",
"upload_time_iso_8601": "2025-06-21T05:51:39.699877Z",
"url": "https://files.pythonhosted.org/packages/9b/c6/6050c8e831ad0f4d9e6dbabcc532152751a409fe0b662aea74a0d20c092f/streamlit_funplayer-0.1.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-06-21 05:51:39",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "B4PT0R",
"github_project": "streamlit-funplayer",
"travis_ci": false,
"coveralls": false,
"github_actions": false,
"requirements": [
{
"name": "streamlit",
"specs": []
},
{
"name": "streamlit-funplayer",
"specs": []
}
],
"lcname": "streamlit-funplayer"
}