streamlit-funplayer


Namestreamlit-funplayer JSON
Version 0.1.0 PyPI version JSON
download
home_pagehttps://github.com/B4PT0R/streamlit-funplayer
SummaryStreamlit component for synchronized media and haptic playback using funscripts and Buttplug.io compatible devices
upload_time2025-06-21 05:51:39
maintainerNone
docs_urlNone
authorBaptiste Ferrand
requires_python>=3.9
licenseMIT
keywords streamlit component media-player haptic-feedback funscript buttplug adult-tech interactive-media vr react
VCS
bugtrack_url
requirements streamlit streamlit-funplayer
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # 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"
}
        
Elapsed time: 0.83149s