# Polanger VideoHub - Developer Hooks Documentation

This document provides a comprehensive reference for all hooks (actions and filters) available in Polanger VideoHub and its addons. Use these hooks to extend functionality, integrate with third-party services, or build custom addons.

---

> **Hook API Stability:** `Public`  
> Backward compatibility guaranteed within the same major version.
>
> **Hook API Version:** `1.0`

---

## Official Resources

- Product page: https://polanger.com/product/polanger-videohub-for-wordpress/
- Documentation: https://polanger.com/polanger-videohub/docs/
- Demo: https://polanger.com/polanger-videohub/
- Developer documentation: https://polanger.com/polanger-videohub/developer/
- GitHub: https://github.com/Polanger/polanger-videohub-lite

---

## Table of Contents

1. [Video Lifecycle Hooks](#video-lifecycle-hooks)
2. [Video Engine API](#video-engine-api)
3. [Template Action Hooks](#template-action-hooks)
4. [Filter Hooks](#filter-hooks)
5. [AJAX Hooks](#ajax-hooks)
6. [Addon-Specific Hooks](#addon-specific-hooks)
7. [Usage Examples](#usage-examples)
8. [Building Custom Addons](#building-custom-addons)
9. [Uploadable Addon Packages](#uploadable-addon-packages)
10. [Complete Hook Inventory](#complete-hook-inventory)

---

## Video Engine API

VideoHub manages videos so other plugins do not have to.

Use this API when another WordPress plugin, theme, LMS, WooCommerce extension, membership plugin, or forum/community plugin wants to use VideoHub videos without rebuilding video upload, player, thumbnail, playlist, premium access, live streaming, or optimization logic.

### Core Rule

Store a VideoHub video ID in your own post meta, term meta, user meta, or custom table. Then ask VideoHub to render the player, card, thumbnail data, or access state.

Do not store raw video URLs unless you have a very specific reason. Raw URLs bypass VideoHub features and make integrations harder to maintain.

### Helper Functions

| Function | Returns | Use case |
|----------|---------|----------|
| `pvh_get_video( $video_id, $args = array() )` | `WP_Post|null` | Safely get a VideoHub video post. |
| `pvh_get_video_data( $video_id, $args = array() )` | `array` | Get title, permalink, thumbnail, duration, views, source, and access state. |
| `pvh_get_video_preview_data( $video_id, $image_size = 'medium_large' )` | `array` | Build cards or external listings without writing thumbnail logic. |
| `pvh_get_video_player( $video_id, $args = array() )` | `string` | Get VideoHub player HTML. |
| `pvh_get_videos( $args = array() )` | `array` | Query VideoHub videos by category, tag, IDs, author, search, or limit. |
| `pvh_render_video( $video_id, $args = array() )` | `string` | Get a complete VideoHub render wrapper for a template. |
| `pvh_the_video( $video_id, $args = array() )` | `void` | Echo the complete VideoHub render wrapper directly. |
| `pvh_get_video_card( $video_id, $args = array() )` | `string` | Render a VideoHub-style video card. |
| `pvh_user_can_watch_video( $video_id, $user_id = 0 )` | `bool` | Check if a user can watch, including Premium Content access when active. |
| `pvh_get_video_access_state( $video_id, $user_id = 0 )` | `array` | Get a detailed access state for custom UI. |
| `pvh_get_attached_video_id( $post_id, $meta_key = '' )` | `int` | Get the VideoHub video ID attached to another WordPress post. |
| `pvh_render_video_picker_field( $args = array() )` | `string` | Render a reusable admin field for selecting a VideoHub video. |
| `pvh_enqueue_video_picker_assets()` | `void` | Load picker assets on custom admin screens. |

### Example: Render a VideoHub Video in Any Template

```php
$video_id = 123;

echo pvh_render_video( $video_id, array(
    'context' => 'custom_template',
) );
```

### Example: Add a VideoHub Picker to LMS Lessons

The fastest LMS integration is one filter. VideoHub adds a meta box to the selected post types and stores the selected video ID.

```php
add_filter( 'pvh_video_picker_post_types', function( $post_types ) {
    $post_types[] = 'lesson';
    $post_types[] = 'sfwd-lessons';

    return $post_types;
} );
```

The default meta key is:

```php
_pvh_attached_video_id
```

Render it on the lesson template:

```php
$video_id = pvh_get_attached_video_id( get_the_ID() );

if ( $video_id ) {
    echo pvh_render_video( $video_id, array(
        'context' => 'lms_lesson',
    ) );
}
```

### Example: Query Videos for Custom Layouts

```php
$videos = pvh_get_videos( array(
    'category' => 'education',
    'limit'    => 12,
) );

foreach ( $videos as $video ) {
    echo pvh_get_video_card( $video->ID );
}
```

### Example: Use a Custom Meta Key

```php
add_filter( 'pvh_video_picker_meta_key', function( $meta_key, $post_type ) {
    if ( 'lesson' === $post_type ) {
        return '_lesson_videohub_video_id';
    }

    return $meta_key;
}, 10, 2 );
```

### Example: Manual Picker Field in Your Own Meta Box

When you render the picker manually, your plugin must also save the submitted meta value. The helper renders the field and loads the modal; it does not save your custom meta box automatically.

```php
add_action( 'admin_enqueue_scripts', function() {
    if ( function_exists( 'pvh_enqueue_video_picker_assets' ) ) {
        pvh_enqueue_video_picker_assets();
    }
} );

echo pvh_render_video_picker_field( array(
    'name'        => '_my_lesson_videohub_video_id',
    'selected'    => absint( get_post_meta( get_the_ID(), '_my_lesson_videohub_video_id', true ) ),
    'label'       => 'Lesson Video',
    'description' => 'Choose a video from VideoHub. Your plugin only stores the video ID.',
) );
```

### Access State Example

```php
$state = pvh_get_video_access_state( $video_id, get_current_user_id() );

if ( $state['can_watch'] ) {
    echo pvh_render_video( $video_id, array( 'context' => 'lesson' ) );
} elseif ( $state['requires_login'] ) {
    echo '<a href="' . esc_url( wp_login_url() ) . '">Login to watch</a>';
} elseif ( $state['is_premium'] ) {
    echo '<p>This lesson uses premium video content.</p>';
}
```

### Engine Integration Hooks

| Hook | Type | Description |
|------|------|-------------|
| `pvh_video_picker_post_types` | Filter | Register external post types that should receive the automatic VideoHub picker. |
| `pvh_video_picker_meta_key` | Filter | Change where the automatic picker stores the selected video ID. |
| `pvh_engine_get_video` | Filter | Modify the resolved video post returned by `pvh_get_video()`. |
| `pvh_engine_get_videos_query_args` | Filter | Modify the query used by `pvh_get_videos()`. |
| `pvh_engine_get_videos` | Filter | Modify results returned by `pvh_get_videos()`. |
| `pvh_engine_video_data` | Filter | Modify structured video data returned by `pvh_get_video_data()`. |
| `pvh_engine_video_card_html` | Filter | Modify card HTML returned by `pvh_get_video_card()`. |
| `pvh_attached_video_id` | Filter | Modify the video ID returned by `pvh_get_attached_video_id()`. |
| `pvh_user_can_watch_video` | Filter | Modify access decisions used by the engine API. |
| `pvh_video_access_state` | Filter | Modify detailed access state data. |
| `pvh_before_video_engine_render` | Action | Runs before `pvh_render_video()` returns engine HTML. |
| `pvh_after_video_engine_render` | Action | Runs after `pvh_render_video()` builds engine HTML. |
| `pvh_engine_render_video_html` | Filter | Modify full render HTML from `pvh_render_video()`. |
| `pvh_engine_unavailable_video_html` | Filter | Customize fallback HTML for unavailable videos. |
| `pvh_engine_picker_post_statuses` | Filter | Change which video statuses appear in the admin picker search. |

---

## Video Lifecycle Hooks

These hooks fire at key moments in a video's lifecycle, enabling integrations with LMS, APIs, caching systems, search indexes, and analytics.

### `pvh_video_uploaded`

Fires when a video file is uploaded via the frontend upload form.

```php
do_action( 'pvh_video_uploaded', int $video_id, string $file_path );
```

| Parameter | Type | Description |
|-----------|------|-------------|
| `$video_id` | int | The created video post ID |
| `$file_path` | string | Full path to the uploaded video file |

**Example:**
```php
add_action( 'pvh_video_uploaded', function( $video_id, $file_path ) {
    // Send notification to admin
    wp_mail( 'admin@example.com', 'New Video Uploaded', 'Video ID: ' . $video_id );
}, 10, 2 );
```

---

### `pvh_video_imported`

Fires when a video is imported from an external source (YouTube, Vimeo, etc.).

```php
do_action( 'pvh_video_imported', int $post_id, string $provider_id, array $video_data );
```

| Parameter | Type | Description |
|-----------|------|-------------|
| `$post_id` | int | The created video post ID |
| `$provider_id` | string | Provider identifier (e.g., 'youtube', 'vimeo') |
| `$video_data` | array | Video metadata from the provider |

**Example:**
```php
add_action( 'pvh_video_imported', function( $post_id, $provider_id, $video_data ) {
    // Log imported video
    error_log( "Video imported from {$provider_id}: {$post_id}" );
}, 10, 3 );
```

---

### `pvh_video_updated`

Fires after a video's metadata is saved/updated.

```php
do_action( 'pvh_video_updated', int $post_id, WP_Post $post );
```

| Parameter | Type | Description |
|-----------|------|-------------|
| `$post_id` | int | The video post ID |
| `$post` | WP_Post | The video post object |

**Example:**
```php
add_action( 'pvh_video_updated', function( $post_id, $post ) {
    // Clear video cache
    wp_cache_delete( 'video_' . $post_id, 'pvh_videos' );
}, 10, 2 );
```

---

### `pvh_video_deleted`

Fires before a video is permanently deleted.

```php
do_action( 'pvh_video_deleted', int $post_id, WP_Post $post );
```

| Parameter | Type | Description |
|-----------|------|-------------|
| `$post_id` | int | The video post ID |
| `$post` | WP_Post | The video post object |

**Example:**
```php
add_action( 'pvh_video_deleted', function( $post_id, $post ) {
    // Remove from external search index
    my_search_index_remove( $post_id );
    
    // Clean up related data
    delete_post_meta( $post_id, '_custom_meta' );
}, 10, 2 );
```

---

### `pvh_video_status_changed`

Fires when a video's publish status changes (e.g., draft → publish, publish → pending).

```php
do_action( 'pvh_video_status_changed', int $post_id, string $old_status, string $new_status );
```

| Parameter | Type | Description |
|-----------|------|-------------|
| `$post_id` | int | The video post ID |
| `$old_status` | string | Previous post status |
| `$new_status` | string | New post status |

**Example:**
```php
add_action( 'pvh_video_status_changed', function( $post_id, $old_status, $new_status ) {
    if ( $new_status === 'publish' && $old_status !== 'publish' ) {
        // Video just published - notify subscribers
        my_notify_subscribers( $post_id );
    }
    
    if ( $new_status === 'trash' ) {
        // Video moved to trash - update analytics
        my_analytics_track( 'video_trashed', $post_id );
    }
}, 10, 3 );
```

---

### `pvh_video_optimized`

Fires after a video has been optimized by the Media Optimizer addon.

```php
do_action( 'pvh_video_optimized', int $video_id, string $output_file, string $output_url, array $stats );
```

| Parameter | Type | Description |
|-----------|------|-------------|
| `$video_id` | int | The video post ID |
| `$output_file` | string | Path to the optimized video file |
| `$output_url` | string | URL of the optimized video |
| `$stats` | array | Optimization statistics |

**Stats Array:**
```php
$stats = array(
    'original_size'     => 104857600,  // Original file size in bytes
    'optimized_size'    => 52428800,   // Optimized file size in bytes
    'compression_ratio' => 50.0,       // Compression percentage
    'size_reduction'    => 52428800,   // Bytes saved
);
```

**Example:**
```php
add_action( 'pvh_video_optimized', function( $video_id, $output_file, $output_url, $stats ) {
    // Log optimization results
    error_log( sprintf(
        'Video %d optimized: %s reduction',
        $video_id,
        $stats['compression_ratio'] . '%'
    ) );
}, 10, 4 );
```

---

## Cloud Storage Hooks

These hooks are provided by the PVH Cloud Storage addon for integrating with cloud upload workflows.

### `pvh_cloud_queue_item_added`

Fires when a video is added to the cloud upload queue.

```php
do_action( 'pvh_cloud_queue_item_added', int $video_id, string $file_path );
```

| Parameter | Type | Description |
|-----------|------|-------------|
| `$video_id` | int | The video post ID |
| `$file_path` | string | Path to the video file to be uploaded |

**Example:**
```php
add_action( 'pvh_cloud_queue_item_added', function( $video_id, $file_path ) {
    // Log when video is queued for cloud upload
    error_log( "Video {$video_id} queued for cloud upload: {$file_path}" );
}, 10, 2 );
```

---

### `pvh_cloud_cdn_url` (Filter)

Filters the CDN URL before it's returned to the player.

```php
$cdn_url = apply_filters( 'pvh_cloud_cdn_url', string $cdn_url, string $original_url, array $settings );
```

| Parameter | Type | Description |
|-----------|------|-------------|
| `$cdn_url` | string | The rewritten CDN URL |
| `$original_url` | string | The original S3 URL |
| `$settings` | array | Cloud storage settings |

**Example:**
```php
add_filter( 'pvh_cloud_cdn_url', function( $cdn_url, $original_url, $settings ) {
    // Add custom query parameter for analytics
    return add_query_arg( 'source', 'videohub', $cdn_url );
}, 10, 3 );
```

---

### `pvh_cloud_signed_url` (Filter)

Filters the signed URL before it's returned to the player.

```php
$signed_url = apply_filters( 'pvh_cloud_signed_url', string $signed_url, string $url, int $video_id, array $settings );
```

| Parameter | Type | Description |
|-----------|------|-------------|
| `$signed_url` | string | The generated signed URL |
| `$url` | string | The original URL before signing |
| `$video_id` | int | The video post ID |
| `$settings` | array | Cloud storage settings |

**Example:**
```php
add_filter( 'pvh_cloud_signed_url', function( $signed_url, $url, $video_id, $settings ) {
    // Log signed URL generation for premium videos
    if ( get_post_meta( $video_id, '_pvh_is_premium', true ) ) {
        error_log( "Signed URL generated for premium video: {$video_id}" );
    }
    return $signed_url;
}, 10, 4 );
```

---

### Cloud Storage Meta Fields

The Cloud Storage addon stores the following meta data for each video:

| Meta Key | Type | Description |
|----------|------|-------------|
| `_pvh_cloud_status` | string | Upload status: pending, uploading, uploaded, failed |
| `_pvh_cloud_url` | string | Full cloud URL of the video |
| `_pvh_cloud_key` | string | S3 object key (path in bucket) |
| `_pvh_cloud_uploaded_at` | string | Timestamp of successful upload |
| `_pvh_cloud_error` | string | Last error message (if failed) |
| `_pvh_cloud_retry_count` | int | Number of retry attempts |
| `_pvh_cloud_retry_reason` | string | Categorized error reason |
| `_pvh_cloud_failed_at` | string | Timestamp of last failure |
| `_pvh_cloud_last_attempt` | string | Timestamp of last upload attempt |
| `_pvh_cloud_next_retry` | string | Timestamp of next scheduled retry |

**Example - Check if video is on cloud:**
```php
function is_video_on_cloud( $video_id ) {
    $status = get_post_meta( $video_id, '_pvh_cloud_status', true );
    return $status === 'uploaded';
}

function get_cloud_url( $video_id ) {
    if ( is_video_on_cloud( $video_id ) ) {
        return get_post_meta( $video_id, '_pvh_cloud_url', true );
    }
    return false;
}
```

---

## Template Action Hooks

These hooks allow you to inject content at specific locations in templates.

### Single Video Page

| Hook | Location | Description |
|------|----------|-------------|
| `pvh_before_single_video` | Before video content | Add content before the video player |
| `polanger_videohub_video_actions` | Below video title | Add action buttons (like, share, etc.) |
| `polanger_videohub_after_video_details` | After video description | Add content after video details |
| `polanger_videohub_after_video_content` | After main content | Add comments, related content, etc. |
| `pvh_video_sidebar_top` | Top of sidebar | Add content to sidebar (used by Live Chat) |

**Example:**
```php
// Add custom section after video details
add_action( 'polanger_videohub_after_video_details', function( $video_id ) {
    echo '<div class="my-custom-section">';
    echo '<h3>Related Products</h3>';
    // Display related products
    echo '</div>';
} );
```

---

### Archive Pages

| Hook | Location | Description |
|------|----------|-------------|
| `pvh_before_archive_content` | Before video grid | Add content before video listings |
| `pvh_before_archive_grid` | Before archive grid | Add content directly before the grid |
| `pvh_archive_grid_after_item` | After each grid item | Inject content after specific video in archive |
| `pvh_after_archive_grid` | After archive grid | Add content directly after the grid |
| `pvh_before_home_content` | Before home content | Add content to home page |
| `pvh_home_grid_after_item` | After each home grid item | Inject content after specific video in home grid |
| `pvh_before_videos_content` | Before videos page | Add content to videos page |
| `pvh_before_category_content` | Before category page | Add content before category videos |
| `pvh_before_category_grid` | Before category grid | Add content directly before category grid |
| `pvh_category_grid_after_item` | After each category grid item | Inject content after specific video in category |
| `pvh_after_category_grid` | After category grid | Add content directly after category grid |
| `pvh_after_category_content` | After category page | Add content after category videos |
| `pvh_before_tag_content` | Before tag page | Add content before tag videos |
| `pvh_after_tag_content` | After tag page | Add content after tag videos |

#### Grid Item Hooks (for Ads, Custom Content)

These hooks fire after each video item in grids, perfect for injecting ads or custom content.

```php
// Category grid - fires after each video
do_action( 'pvh_category_grid_after_item', int $grid_index, int $video_id, WP_Term $term );

// Archive grid - fires after each video
do_action( 'pvh_archive_grid_after_item', int $grid_index, int $video_id );

// Homepage grid - fires after each video
do_action( 'pvh_home_grid_after_item', int $grid_index, int $video_id, string $section_type );
```

| Parameter | Type | Description |
|-----------|------|-------------|
| `$grid_index` | int | Current position in grid (1-indexed) |
| `$video_id` | int | Current video post ID |
| `$term` | WP_Term | Current category term (category grid only) |
| `$section_type` | string | Section type: 'latest', 'trending', etc. (home grid only) |

**Example: Inject ad every 5 videos**
```php
add_action( 'pvh_category_grid_after_item', function( $index, $video_id, $term ) {
    if ( $index % 5 === 0 ) {
        echo '<div class="my-ad-unit">';
        // Your ad code here
        echo '</div>';
    }
}, 10, 3 );
```

---

### Profile & Author Pages

| Hook | Location | Description |
|------|----------|-------------|
| `pvh_before_author_profile` | Before author profile | Add content before profile |
| `pvh_author_header_actions` | Author header | Add action buttons to author header |
| `pvh_after_author_profile` | After author profile | Add content after profile |
| `pvh_profile_header_actions` | Profile header | Add buttons to user's own profile |
| `pvh_channel_header_actions` | Channel header | Add buttons to channel header |

---

### Mobile Navigation

| Hook | Location | Description |
|------|----------|-------------|
| `pvh_mobile_bar_before_items` | Before nav items | Add items to mobile bar start |
| `pvh_mobile_bar_after_home` | After home button | Add items after home |
| `pvh_mobile_bar_after_user` | After user button | Add items after user |
| `pvh_mobile_bar_after_items` | After all items | Add items to mobile bar end |
| `pvh_mobile_user_menu_items` | User menu popup | Add items to mobile user menu |

---

### User Menu

| Hook | Location | Description |
|------|----------|-------------|
| `pvh_user_dropdown_menu_items` | User dropdown | Add items to header user dropdown |

```php
do_action( 'pvh_user_dropdown_menu_items', WP_User $user );
```

| Parameter | Type | Description |
|-----------|------|-------------|
| `$user` | WP_User | The current logged-in user object |

**Example:**
```php
// Add custom link to user dropdown
add_action( 'pvh_user_dropdown_menu_items', function( $user ) {
    echo '<a href="/my-page" class="pvh-user-dropdown-item">';
    echo '<svg>...</svg> My Custom Page';
    echo '</a>';
} );
```

---

## Filter Hooks

Filters allow you to modify data before it's used or displayed.

### `pvh_video_player_args`

Filter video player arguments before rendering.

```php
$args = apply_filters( 'pvh_video_player_args', array $args, int $video_id );
```

| Parameter | Type | Description |
|-----------|------|-------------|
| `$args` | array | Player arguments (autoplay, controls, width, height) |
| `$video_id` | int | The video post ID |

**Example:**
```php
add_filter( 'pvh_video_player_args', function( $args, $video_id ) {
    // Disable autoplay for premium videos
    if ( get_post_meta( $video_id, '_is_premium', true ) ) {
        $args['autoplay'] = false;
    }
    
    // Add custom poster
    $args['poster'] = get_post_meta( $video_id, '_custom_poster', true );
    
    return $args;
}, 10, 2 );
```

---

### `pvh_video_player_source`

Filter the video source URL before playback.

```php
$url = apply_filters( 'pvh_video_player_source', string $url, int $video_id );
```

**Example:**
```php
add_filter( 'pvh_video_player_source', function( $url, $video_id ) {
    // Add authentication token to URL
    return add_query_arg( 'token', generate_video_token( $video_id ), $url );
}, 10, 2 );
```

---

### `polanger_videohub_video_player`

Filter the complete video player HTML output.

```php
$html = apply_filters( 'polanger_videohub_video_player', string $html, int $video_id, array $args );
```

**Example:**
```php
add_filter( 'polanger_videohub_video_player', function( $html, $video_id, $args ) {
    // Wrap player in custom container
    return '<div class="my-player-wrapper">' . $html . '</div>';
}, 10, 3 );
```

---

### `pvh_settings_tabs`

Add custom tabs to the settings page.

```php
$tabs = apply_filters( 'pvh_settings_tabs', array $tabs );
```

**Example:**
```php
add_filter( 'pvh_settings_tabs', function( $tabs ) {
    $tabs['my-addon'] = array(
        'label' => 'My Addon Settings',
        'icon'  => 'dashicons-admin-generic',
    );
    return $tabs;
} );

// Then add content for the tab
add_action( 'pvh_settings_tab_content_my-addon', function() {
    echo '<h2>My Addon Settings</h2>';
    // Render settings form
} );
```

---

### `pvh_profile_tabs`

Add custom tabs to user profile pages.

```php
$tabs = apply_filters( 'pvh_profile_tabs', array $tabs, WP_User $user, bool $is_own_profile );
```

**Example:**
```php
add_filter( 'pvh_profile_tabs', function( $tabs, $user, $is_own_profile ) {
    $tabs['analytics'] = array(
        'label' => 'Analytics',
        'icon'  => '<svg>...</svg>',
    );
    return $tabs;
}, 10, 3 );

// Add tab content
add_action( 'pvh_profile_tab_content_analytics', function( $user, $is_own_profile ) {
    if ( $is_own_profile ) {
        // Show analytics dashboard
    }
}, 10, 2 );
```

---

## AJAX Hooks

### Upload Hooks

| Hook | When | Parameters |
|------|------|------------|
| `pvh_before_ajax_upload` | Before upload processing | `$file`, `$user_id`, `$settings` |
| `pvh_after_ajax_upload` | After successful upload | `$video_id`, `$attachment_id`, `$upload`, `$user_id` |
| `pvh_video_details_saved` | After video details saved | `$video_id`, `$data` |
| `pvh_upload_form_after_details` | In upload form, after details | `$video_id`, `$settings` |

**Example:**
```php
// Log all upload attempts
add_action( 'pvh_before_ajax_upload', function( $file, $user_id, $settings ) {
    error_log( "Upload attempt by user {$user_id}: {$file['name']}" );
}, 10, 3 );

// Track successful uploads
add_action( 'pvh_after_ajax_upload', function( $video_id, $attachment_id, $upload, $user_id ) {
    // Update user upload count
    $count = (int) get_user_meta( $user_id, 'total_uploads', true );
    update_user_meta( $user_id, 'total_uploads', $count + 1 );
}, 10, 4 );
```

---

## Addon-Specific Hooks

### Auth Addon

| Hook | Type | Description |
|------|------|-------------|
| `pvh_user_dropdown_menu_items` | Action | Add items to user dropdown |
| `pvh_profile_header_actions` | Action | Add buttons to profile header |
| `pvh_profile_tabs` | Filter | Add profile tabs |
| `pvh_profile_tab_content_{key}` | Action | Render profile tab content |

### Comments Addon

Uses WordPress standard `get_avatar` and `get_avatar_url` filters for custom avatars.

### Shorts Addon

| Hook | Type | Description |
|------|------|-------------|
| `pvh_before_shorts_archive` | Action | Before shorts archive content |
| `pvh_before_shorts_feed` | Action | Before shorts feed container |
| `pvh_shorts_feed_after_item` | Action | After each short in feed (for ads) |
| `pvh_after_shorts_feed` | Action | After shorts feed container |

#### Shorts Feed Item Hook

Fires after each short item in the feed, perfect for injecting ads between shorts.

```php
do_action( 'pvh_shorts_feed_after_item', int $shorts_index, int $short_id );
```

| Parameter | Type | Description |
|-----------|------|-------------|
| `$shorts_index` | int | Current position in feed (1-indexed) |
| `$short_id` | int | Current short post ID |

**Example: Inject ad every 5 shorts**
```php
add_action( 'pvh_shorts_feed_after_item', function( $index, $short_id ) {
    if ( $index % 5 === 0 ) {
        echo '<div class="my-shorts-ad">';
        // Your ad code here
        echo '</div>';
    }
}, 10, 2 );
```

### Search Addon

The Search addon provides extensive hooks for customization and third-party integrations.

#### Settings & Configuration

| Hook | Type | Description |
|------|------|-------------|
| `pvh_search_init` | Action | Fires when search addon initializes |
| `pvh_search_settings` | Filter | Filter search settings array |
| `pvh_search_filter_enabled` | Filter | Check if specific filter is enabled |
| `pvh_search_settings_fields` | Action | Add custom fields to search settings |
| `pvh_search_settings_save` | Filter | Filter settings before saving |
| `pvh_search_settings_saved` | Action | After search settings are saved |

#### Search Bar Rendering

| Hook | Type | Description |
|------|------|-------------|
| `pvh_before_search_bar_render` | Action | Before search bar HTML is rendered |
| `pvh_after_search_bar` | Action | After search bar (user menu location) |
| `pvh_after_search_bar_render` | Action | After search bar HTML is rendered |
| `pvh_search_before_type_chips` | Action | Before type filter chips (All, Videos, etc.) |
| `pvh_search_after_type_chips` | Action | After type filter chips |
| `pvh_search_before_filter_groups` | Action | Before filter groups in panel |
| `pvh_search_after_filter_groups` | Action | After filter groups (add custom filters) |
| `pvh_search_filter_categories` | Filter | Filter categories shown in search |
| `pvh_before_search_results` | Action | Before search results |

**Example: Disable Duration Filter Programmatically**
```php
add_filter( 'pvh_search_filter_enabled', function( $enabled, $filter ) {
    // Disable duration filter for non-logged-in users
    if ( $filter === 'duration' && ! is_user_logged_in() ) {
        return false;
    }
    return $enabled;
}, 10, 2 );
```

**Example: Add Custom Filter Group**
```php
add_action( 'pvh_search_after_filter_groups', function( $settings ) {
    ?>
    <div class="pvh-filter-group">
        <div class="pvh-filter-group-header">
            <svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor">
                <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2z"/>
            </svg>
            <span><?php esc_html_e( 'Quality', 'my-addon' ); ?></span>
        </div>
        <div class="pvh-filter-options" data-filter="quality">
            <button type="button" class="pvh-filter-btn" data-value="hd">HD</button>
            <button type="button" class="pvh-filter-btn" data-value="4k">4K</button>
        </div>
    </div>
    <?php
} );
```

**Example: Add Custom Settings Field**
```php
add_action( 'pvh_search_settings_fields', function( $settings ) {
    ?>
    <table class="form-table">
        <tr>
            <th scope="row"><?php esc_html_e( 'Custom Option', 'my-addon' ); ?></th>
            <td>
                <input type="text" name="pvh_search_settings[custom_option]" 
                       value="<?php echo esc_attr( $settings['custom_option'] ?? '' ); ?>">
            </td>
        </tr>
    </table>
    <?php
} );

// Save custom field
add_filter( 'pvh_search_settings_save', function( $settings, $raw ) {
    $settings['custom_option'] = sanitize_text_field( $raw['custom_option'] ?? '' );
    return $settings;
}, 10, 2 );
```

**Example: Modify Search Settings**
```php
add_filter( 'pvh_search_settings', function( $settings, $defaults ) {
    // Force enable all filters for admins
    if ( current_user_can( 'manage_options' ) ) {
        $settings['filters'] = array(
            'upload_date' => true,
            'duration'    => true,
            'sort_by'     => true,
            'category'    => true,
        );
    }
    return $settings;
}, 10, 2 );
```

### Live Chat Addon

| Hook | Type | Description |
|------|------|-------------|
| `pvh_chat_transport` | Filter | Modify chat transport method |
| `pvh_chat_sanitize_message` | Filter | Sanitize chat messages |

### Live Streaming Addon

| Hook | Type | Description |
|------|------|-------------|
| `pvh_live_stream_sidebar_top` | Action | Top of live stream sidebar |

### Premium Content Addon

| Hook | Type | Description |
|------|------|-------------|
| `pvh_upload_form_after_details` | Action | Renders premium fields in upload form |
| `pvh_video_details_saved` | Action | Saves premium fields from upload form |

### Ads Addon

The Ads addon provides hooks for ad placement, tracking, and advanced customization.

#### Ad Placement Hooks

These existing hooks are used for ad placement:

| Hook | Type | Description |
|------|------|-------------|
| `pvh_before_single_video` | Action | Above video player ad position |
| `polanger_videohub_after_video_details` | Action | Below video player ad position |
| `polanger_videohub_after_video_content` | Action | Below comments ad position |
| `pvh_category_grid_after_item` | Action | In-feed ads in category grids |
| `pvh_archive_grid_after_item` | Action | In-feed ads in archive grids |
| `pvh_home_grid_after_item` | Action | In-feed ads in homepage grids |
| `pvh_shorts_feed_after_item` | Action | Ads between shorts |

#### Ad Control Hooks (New in 1.6.0)

##### `pvh_ads_should_show`

Filter whether ads should be shown for a specific context.

```php
apply_filters( 'pvh_ads_should_show', bool $show, int $video_id, int $user_id, string $slot );
```

| Parameter | Type | Description |
|-----------|------|-------------|
| `$show` | bool | Whether to show ads (default based on settings) |
| `$video_id` | int | Current video ID (0 if not on video page) |
| `$user_id` | int | Current user ID (0 if not logged in) |
| `$slot` | string | Ad slot identifier (empty for general check) |

**Example:**
```php
// Disable ads for specific user roles
add_filter( 'pvh_ads_should_show', function( $show, $video_id, $user_id, $slot ) {
    if ( $user_id && user_can( $user_id, 'subscriber' ) ) {
        return false; // No ads for subscribers
    }
    return $show;
}, 10, 4 );

// Disable ads for specific video categories
add_filter( 'pvh_ads_should_show', function( $show, $video_id, $user_id, $slot ) {
    if ( $video_id && has_term( 'kids', 'pvh_category', $video_id ) ) {
        return false; // No ads on kids content
    }
    return $show;
}, 10, 4 );
```

---

##### `pvh_ad_slot_before_render`

Filter ad content before rendering. Allows modification based on slot type, video, or other conditions.

```php
apply_filters( 'pvh_ad_slot_before_render', array $ad_data, string $slot, int $video_id );
```

| Parameter | Type | Description |
|-----------|------|-------------|
| `$ad_data` | array | Ad data with keys: `type`, `code`, `image`, `url` |
| `$slot` | string | Ad slot identifier |
| `$video_id` | int | Current video ID |

**Example:**
```php
// Use different ad for gaming category
add_filter( 'pvh_ad_slot_before_render', function( $ad_data, $slot, $video_id ) {
    if ( $video_id && has_term( 'gaming', 'pvh_category', $video_id ) ) {
        $ad_data['code'] = '<div class="gaming-ad">Gaming sponsor here</div>';
    }
    return $ad_data;
}, 10, 3 );
```

---

##### `pvh_ad_unit_html`

Filter the final ad unit HTML output.

```php
apply_filters( 'pvh_ad_unit_html', string $output, string $slot, int $video_id );
```

**Example:**
```php
// Add wrapper div to all ads
add_filter( 'pvh_ad_unit_html', function( $output, $slot, $video_id ) {
    return '<div class="my-ad-wrapper" data-slot="' . esc_attr( $slot ) . '">' . $output . '</div>';
}, 10, 3 );
```

---

#### Ad Event Hooks (New in 1.6.0)

##### `pvh_ad_event_tracked`

Fires when any ad event (impression or click) is tracked.

```php
do_action( 'pvh_ad_event_tracked', string $slot, string $event, int $video_id, int $user_id );
```

| Parameter | Type | Description |
|-----------|------|-------------|
| `$slot` | string | Ad slot identifier |
| `$event` | string | Event type ('impression' or 'click') |
| `$video_id` | int | Video ID (0 if not available) |
| `$user_id` | int | User ID (0 if not logged in) |

**Example:**
```php
// Send ad events to external analytics
add_action( 'pvh_ad_event_tracked', function( $slot, $event, $video_id, $user_id ) {
    // Send to Google Analytics, Mixpanel, etc.
    do_action( 'my_analytics_track', 'ad_' . $event, array(
        'slot' => $slot,
        'video_id' => $video_id,
    ) );
}, 10, 4 );
```

---

##### `pvh_ad_completed`

Fires when an ad interaction is completed (clicked).

```php
do_action( 'pvh_ad_completed', string $slot, int $video_id, int $user_id );
```

**Example:**
```php
// Award points when user interacts with ad
add_action( 'pvh_ad_completed', function( $slot, $video_id, $user_id ) {
    if ( $user_id ) {
        // Award 5 points for ad interaction
        update_user_meta( $user_id, 'ad_points', 
            intval( get_user_meta( $user_id, 'ad_points', true ) ) + 5 
        );
    }
}, 10, 3 );
```

---

#### Playback Milestone Hook (New in 1.6.0)

##### `pvh_video_playback_milestone`

Fires when a video playback milestone is reached. Useful for analytics, ad triggering, or engagement tracking.

```php
do_action( 'pvh_video_playback_milestone', int $video_id, int $milestone, int $user_id );
```

| Parameter | Type | Description |
|-----------|------|-------------|
| `$video_id` | int | Video post ID |
| `$milestone` | int | Milestone percentage (25, 50, 75, or 100) |
| `$user_id` | int | User ID (0 if not logged in) |

**Example:**
```php
// Trigger mid-roll ad at 50% milestone
add_action( 'pvh_video_playback_milestone', function( $video_id, $milestone, $user_id ) {
    if ( $milestone === 50 ) {
        // Log engagement
        error_log( "User {$user_id} reached 50% of video {$video_id}" );
    }
    
    if ( $milestone === 100 ) {
        // Video completed - update watch history
        if ( $user_id ) {
            $history = get_user_meta( $user_id, 'completed_videos', true ) ?: array();
            $history[] = $video_id;
            update_user_meta( $user_id, 'completed_videos', array_unique( $history ) );
        }
    }
}, 10, 3 );
```

---

#### JavaScript API

The Ads addon exposes a global `PVH_Ads` object for custom integrations:

```javascript
// Track custom ad event
PVH_Ads.trackEvent('custom-slot', 'impression');
PVH_Ads.trackEvent('custom-slot', 'click');

// Track ad completion with watch duration
PVH_Ads.trackAdCompletion('preroll', 15); // 15 seconds watched

// Track custom milestone
PVH_Ads.trackMilestone(50); // 50% milestone

// Check if ads should show
if (PVH_Ads.shouldShowAds) {
    // Show custom ad
}

// Listen to milestone events
$(document).on('pvh:milestone', function(e, data) {
    console.log('Milestone reached:', data.milestone, 'for video:', data.videoId);
});

// Listen to ad completion events
$(document).on('pvh:ad:completed', function(e, data) {
    console.log('Ad completed:', data.slot, 'watch duration:', data.watchDuration);
});
```

#### Ad Statistics Table

The addon creates a `wp_pvh_ad_stats` table with the following structure:

| Column | Type | Description |
|--------|------|-------------|
| `id` | bigint | Primary key |
| `ad_slot` | varchar(50) | Ad slot identifier |
| `event_type` | enum | 'impression' or 'click' |
| `video_id` | bigint | Associated video ID |
| `user_id` | bigint | User ID (if logged in) |
| `ip_address` | varchar(45) | Visitor IP |
| `created_at` | datetime | Event timestamp |

---

### Marketplace Addon

| Hook | Type | Description |
|------|------|-------------|
| `pvh_marketplace_earning_created` | Action | After an earning record is created |
| `pvh_marketplace_earning_status_changed` | Action | After earning status changes |
| `pvh_marketplace_withdrawal_created` | Action | After withdrawal request created |
| `pvh_marketplace_withdrawal_paid` | Action | After withdrawal marked as paid |
| `pvh_marketplace_withdrawal_rejected` | Action | After withdrawal rejected |
| `pvh_marketplace_earnings_released` | Action | After pending earnings released by cron |
| `pvh_marketplace_refund_withdrawn_attempt` | Action | When trying to refund withdrawn earning |
| `pvh_marketplace_refund_withdrawn_earnings` | Action | When refund affects withdrawn earnings |
| `pvh_marketplace_vendor_profile_saved` | Action | After vendor payout profile saved |
| `pvh_marketplace_release_earnings` | Cron | Scheduled event to release pending earnings |

**Example:**
```php
// Notify vendor when earning is created
add_action( 'pvh_marketplace_earning_created', function( $earning_id, $data ) {
    $vendor_id = $data['vendor_id'];
    $amount = $data['net_amount'];
    
    // Send notification
    wp_mail( 
        get_userdata( $vendor_id )->user_email, 
        'New Sale!', 
        "You earned $amount from a video sale."
    );
}, 10, 2 );

// Track withdrawals
add_action( 'pvh_marketplace_withdrawal_paid', function( $withdrawal_id, $withdrawal ) {
    // Log to external accounting system
    my_accounting_log( 'payout', $withdrawal->amount, $withdrawal->vendor_id );
}, 10, 2 );
```

---

### Shorts Addon (New in 1.6.0)

| Hook | Type | Description |
|------|------|-------------|
| `pvh_shorts_query_args` | Filter | Filter shorts feed query arguments |
| `pvh_shorts_before_render` | Action | Fires before shorts feed is rendered |
| `pvh_short_viewed` | Action | Fires when a short is viewed |
| `pvh_short_liked` | Action | Fires when a short is liked/unliked |
| `pvh_short_data` | Filter | Filter short data array before output |

**Example:**
```php
// Track short views for analytics
add_action( 'pvh_short_viewed', function( $short_id, $views, $user_id ) {
    // Log to analytics system
    my_analytics_track( 'short_view', array(
        'short_id' => $short_id,
        'views'    => $views,
        'user_id'  => $user_id,
    ) );
}, 10, 3 );

// Add custom data to shorts
add_filter( 'pvh_short_data', function( $data, $short_id ) {
    $data['custom_field'] = get_post_meta( $short_id, '_my_custom_field', true );
    return $data;
}, 10, 2 );

// Modify shorts query for AI recommendations
add_filter( 'pvh_shorts_query_args', function( $args, $atts ) {
    if ( is_user_logged_in() ) {
        // Add personalized ordering
        $args['meta_key'] = '_recommendation_score';
        $args['orderby'] = 'meta_value_num';
    }
    return $args;
}, 10, 2 );
```

---

### Search Addon (New in 1.6.0)

| Hook | Type | Description |
|------|------|-------------|
| `pvh_search_query_args` | Filter | Filter WP_Query arguments before search |
| `pvh_search_results` | Filter | Filter search results before returning |
| `pvh_search_suggestions` | Filter | Filter search suggestions |
| `pvh_search_video_data` | Filter | Filter individual video data in results |

**Example:**
```php
// Add AI semantic search
add_filter( 'pvh_search_query_args', function( $args, $query, $filters ) {
    // Integrate with AI search service
    $ai_results = my_ai_search( $query );
    if ( ! empty( $ai_results ) ) {
        $args['post__in'] = $ai_results;
        $args['orderby'] = 'post__in';
    }
    return $args;
}, 10, 3 );

// Add custom data to search results
add_filter( 'pvh_search_video_data', function( $data, $video_id ) {
    $data['relevance_score'] = calculate_relevance( $video_id );
    return $data;
}, 10, 2 );

// Enhance suggestions with trending terms
add_filter( 'pvh_search_suggestions', function( $suggestions, $query ) {
    $trending = get_trending_searches();
    return array_merge( $suggestions, $trending );
}, 10, 2 );
```

---

### Reactions Addon (New in 1.6.0)

| Hook | Type | Description |
|------|------|-------------|
| `pvh_reaction_added` | Action | Fires when a reaction is added |
| `pvh_reaction_removed` | Action | Fires when a reaction is removed |
| `pvh_can_react` | Filter | Filter whether user can react |
| `pvh_reaction_counts` | Filter | Filter reaction counts before display |

**Example:**
```php
// Track reactions for analytics
add_action( 'pvh_reaction_added', function( $video_id, $user_id, $reaction, $action_type, $previous ) {
    // Log reaction event
    my_analytics_track( 'reaction', array(
        'video_id'    => $video_id,
        'reaction'    => $reaction,
        'action_type' => $action_type, // 'added' or 'switched'
    ) );
}, 10, 5 );

// Disable reactions for certain videos
add_filter( 'pvh_can_react', function( $can_react, $video_id, $user_id ) {
    // Disable reactions for archived videos
    if ( get_post_meta( $video_id, '_archived', true ) ) {
        return false;
    }
    return $can_react;
}, 10, 3 );
```

---

### Comments Addon (New in 1.6.0)

| Hook | Type | Description |
|------|------|-------------|
| `pvh_comment_added` | Action | Fires after a comment is added |
| `pvh_comment_edited` | Action | Fires after a comment is edited |
| `pvh_comment_deleted` | Action | Fires after a comment is deleted |
| `pvh_comment_voted` | Action | Fires after a comment vote |
| `pvh_can_comment` | Filter | Filter whether user can comment |
| `pvh_comment_content` | Filter | Filter comment content (AI moderation) |

**Example:**
```php
// AI content moderation
add_filter( 'pvh_comment_content', function( $content ) {
    // Check with AI moderation service
    $result = my_ai_moderate( $content );
    if ( $result['is_toxic'] ) {
        // Replace toxic content
        return '[This comment was removed by AI moderation]';
    }
    return $content;
} );

// Notify video owner of new comments
add_action( 'pvh_comment_added', function( $comment_id, $video_id, $user_id, $content, $parent_id, $status ) {
    $video = get_post( $video_id );
    if ( $video && $video->post_author != $user_id ) {
        // Send notification to video owner
        wp_mail( 
            get_userdata( $video->post_author )->user_email,
            'New comment on your video',
            "Someone commented on your video: " . get_the_title( $video_id )
        );
    }
}, 10, 6 );

// Restrict commenting to subscribers only
add_filter( 'pvh_can_comment', function( $can_comment, $video_id, $user_id ) {
    if ( ! $user_id ) return false;
    
    // Check if user is subscribed to video author's channel
    $video = get_post( $video_id );
    $channel = pvh_channels()->get_user_channel( $video->post_author );
    if ( $channel && ! pvh_channels()->is_subscribed( $channel->ID, $user_id ) ) {
        return false;
    }
    return $can_comment;
}, 10, 3 );
```

---

### Uploads Addon (New in 1.6.0)

| Hook | Type | Description |
|------|------|-------------|
| `pvh_can_upload` | Filter | Filter whether user can upload |
| `pvh_upload_settings` | Filter | Filter upload settings |
| `pvh_upload_max_size` | Filter | Filter max upload size (role-based) |
| `pvh_upload_file_types` | Filter | Filter allowed file types |
| `pvh_video_published` | Action | Fires when video is published |

**Example:**
```php
// Premium users get larger upload limits
add_filter( 'pvh_upload_max_size', function( $max_size, $user_id, $settings ) {
    if ( user_can( $user_id, 'premium_member' ) ) {
        return 2 * 1024 * 1024 * 1024; // 2GB for premium
    }
    return $max_size;
}, 10, 3 );

// Allow additional formats for verified creators
add_filter( 'pvh_upload_file_types', function( $formats, $user_id ) {
    if ( get_user_meta( $user_id, '_verified_creator', true ) ) {
        $formats[] = 'mkv';
        $formats[] = 'flv';
    }
    return $formats;
}, 10, 2 );

// Trigger AI content scanning on publish
add_action( 'pvh_video_published', function( $video_id, $video, $user_id ) {
    // Queue video for AI content analysis
    wp_schedule_single_event( time(), 'my_ai_scan_video', array( $video_id ) );
}, 10, 3 );
```

---

### Live Chat Addon (New in 1.6.0)

| Hook | Type | Description |
|------|------|-------------|
| `pvh_chat_message_sent` | Action | Fires after a chat message is sent |
| `pvh_chat_message_deleted` | Action | Fires after a chat message is deleted |
| `pvh_chat_message_content` | Filter | Filter message content (AI moderation) |
| `pvh_chat_can_send` | Filter | Filter whether user can send messages |

**Example:**
```php
// AI chat moderation
add_filter( 'pvh_chat_message_content', function( $message ) {
    // Profanity filter
    $message = my_profanity_filter( $message );
    
    // AI toxicity check
    if ( my_ai_is_toxic( $message ) ) {
        return ''; // Block message
    }
    
    return $message;
} );

// Log chat messages for analytics
add_action( 'pvh_chat_message_sent', function( $message_id, $room_id, $user_id, $message ) {
    // Track chat engagement
    my_analytics_track( 'chat_message', array(
        'room_id'    => $room_id,
        'user_id'    => $user_id,
        'length'     => strlen( $message ),
    ) );
}, 10, 4 );

// VIP-only chat rooms
add_filter( 'pvh_chat_can_send', function( $can_send, $user_id, $post_id ) {
    // Check if this is a VIP-only stream
    if ( get_post_meta( $post_id, '_vip_chat_only', true ) ) {
        return user_can( $user_id, 'vip_member' );
    }
    return $can_send;
}, 10, 3 );
```

---

### Channels Addon (New in 1.6.0)

| Hook | Type | Description |
|------|------|-------------|
| `pvh_channel_created` | Action | Fires after a channel is created |
| `pvh_channel_updated` | Action | Fires after a channel is updated |
| `pvh_channel_before_delete` | Action | Fires before a channel is deleted |
| `pvh_channel_deleted` | Action | Fires after a channel is deleted |
| `pvh_channel_subscribed` | Action | Fires when user subscribes |
| `pvh_channel_unsubscribed` | Action | Fires when user unsubscribes |

**Example:**
```php
// Welcome email for new channels
add_action( 'pvh_channel_created', function( $channel_id, $user_id, $data ) {
    wp_mail(
        get_userdata( $user_id )->user_email,
        'Your channel is ready!',
        "Congratulations! Your channel '{$data['name']}' has been created."
    );
}, 10, 3 );

// Notify channel owner of new subscribers
add_action( 'pvh_channel_subscribed', function( $channel_id, $subscriber_id, $count ) {
    $channel = get_post( $channel_id );
    $subscriber = get_userdata( $subscriber_id );
    
    // Send notification
    wp_mail(
        get_userdata( $channel->post_author )->user_email,
        'New subscriber!',
        "{$subscriber->display_name} subscribed to your channel. You now have {$count} subscribers!"
    );
}, 10, 3 );

// Analytics tracking
add_action( 'pvh_channel_subscribed', function( $channel_id, $user_id, $count ) {
    my_analytics_track( 'channel_subscribe', array(
        'channel_id' => $channel_id,
        'user_id'    => $user_id,
        'total'      => $count,
    ) );
}, 10, 3 );
```

---

## Building Custom Addons

### Addon Structure

```
my-addon/
|-- my-addon.php          # Main addon file
|-- admin/
|   `-- views/            # Admin templates
|-- public/
|   |-- css/              # Frontend styles
|   |-- js/               # Frontend scripts
|   `-- views/            # Frontend templates
`-- includes/             # PHP classes
```

### Main Addon File Header

```php
<?php
/**
 * Addon Name: My Custom Addon
 * Addon URI: https://example.com/my-addon
 * Description: Description of what this addon does.
 * Version: 1.0.0
 * Author: Your Name
 * Author URI: https://example.com
 * Slug: my-addon
 * Requires: 1.0.0
 */
```

### Essential Hooks for Addon Development

1. **Initialization**: `polanger_videohub_addons_loaded`
2. **Settings Tab**: `pvh_settings_tabs` + `pvh_settings_tab_content_{slug}`
3. **Profile Tab**: `pvh_profile_tabs` + `pvh_profile_tab_content_{key}`
4. **Video Page Content**: `polanger_videohub_after_video_content`
5. **User Menu Items**: `pvh_user_dropdown_menu_items`
6. **Video Import**: `pvh_video_imported`
7. **Video Upload**: `pvh_video_uploaded`

### Example: Simple Analytics Addon

```php
<?php
/**
 * Addon Name: Simple Analytics
 * Slug: simple-analytics
 * Requires: 1.0.0
 */

class My_Analytics_Addon {
    
    public function __construct() {
        // Track video views
        add_action( 'pvh_before_single_video', array( $this, 'track_view' ) );
        
        // Track imports
        add_action( 'pvh_video_imported', array( $this, 'track_import' ), 10, 3 );
        
        // Track status changes
        add_action( 'pvh_video_status_changed', array( $this, 'track_status' ), 10, 3 );
        
        // Add settings tab
        add_filter( 'pvh_settings_tabs', array( $this, 'add_settings_tab' ) );
        add_action( 'pvh_settings_tab_content_analytics', array( $this, 'render_settings' ) );
    }
    
    public function track_view() {
        // Track video view
    }
    
    public function track_import( $post_id, $provider, $data ) {
        // Track import event
    }
    
    public function track_status( $post_id, $old, $new ) {
        // Track status change
    }
    
    public function add_settings_tab( $tabs ) {
        $tabs['analytics'] = array(
            'label' => 'Analytics',
            'icon'  => 'dashicons-chart-bar',
        );
        return $tabs;
    }
    
    public function render_settings() {
        // Render analytics dashboard
    }
}

new My_Analytics_Addon();
```

---

## JavaScript Player Events (Ads & Analytics Integration)

VideoHub 1.5.0 introduces a comprehensive JavaScript player events system, enabling addons to integrate with video playback for ads, analytics, and custom interactions.

### Global Player Object

```javascript
// Access the player API
window.PVH_Player
```

### Available Events

| Event | Description | Data |
|-------|-------------|------|
| `ready` | Player initialized | `{ player, videoId }` |
| `play` | Video started/resumed | `{ videoId, currentTime, duration }` |
| `pause` | Video paused | `{ videoId, currentTime, duration }` |
| `ended` | Video finished | `{ videoId, duration }` |
| `timeupdate` | Time changed (throttled 4/sec) | `{ videoId, currentTime, duration, percent }` |
| `seeking` | User seeking | `{ videoId, currentTime }` |
| `seeked` | Seek completed | `{ videoId, currentTime }` |
| `volumechange` | Volume/mute changed | `{ videoId, volume, muted }` |
| `fullscreen` | Fullscreen toggled | `{ videoId, fullscreen }` |
| `milestone` | Progress milestone reached | `{ videoId, milestone, currentTime, duration }` |
| `cuepoint` | Cue point triggered | `{ videoId, time, index, data, currentTime }` |

### Listening to Events

```javascript
// Method 1: Using PVH_Player.on()
PVH_Player.on('play', function(data) {
    console.log('Video started:', data.videoId);
});

PVH_Player.on('milestone', function(data) {
    if (data.milestone === 50) {
        console.log('User watched 50% of video');
    }
});

// Method 2: Using DOM events
document.addEventListener('pvh:player:play', function(e) {
    console.log('Video started:', e.detail.videoId);
});

document.addEventListener('pvh:player:ended', function(e) {
    // Track video completion
    gtag('event', 'video_complete', { video_id: e.detail.videoId });
});
```

### Cue Points for Mid-Roll Ads

```javascript
// Add cue points for mid-roll ad breaks
PVH_Player.on('ready', function() {
    // Add ad break at 30 seconds
    PVH_Player.addCuePoint(30, { type: 'midroll', adTag: 'https://...' });
    
    // Add ad break at 5 minutes
    PVH_Player.addCuePoint(300, { type: 'midroll', adTag: 'https://...' });
    
    // Add multiple cue points
    PVH_Player.addCuePoints([
        { time: 60, data: { type: 'overlay' } },
        { time: 120, data: { type: 'midroll' } }
    ]);
});

// Listen for cue point triggers
PVH_Player.on('cuepoint', function(data) {
    console.log('Cue point at', data.time, 'seconds');
    
    // Pause video for ad
    PVH_Player.pause();
    
    // Show your ad...
    showAd(data.data.adTag, function() {
        // Resume video after ad
        PVH_Player.play();
    });
});
```

### Player Control Methods

```javascript
// Pause video (for ad playback)
PVH_Player.pause();

// Resume video (after ad)
PVH_Player.play();

// Seek to specific time
PVH_Player.seek(120); // Jump to 2 minutes

// Get current playback position
var currentTime = PVH_Player.getCurrentTime();

// Get video duration
var duration = PVH_Player.getDuration();

// Reset cue points (for replay)
PVH_Player.resetCuePoints();
```

### Pre-Roll Ad Example

```javascript
PVH_Player.on('ready', function(data) {
    // Pause immediately for pre-roll
    PVH_Player.pause();
    
    // Show pre-roll ad container
    var adContainer = document.createElement('div');
    adContainer.className = 'pvh-preroll-ad';
    adContainer.innerHTML = '<div class="ad-content">Your ad here</div>';
    document.querySelector('.pvh-video-player-wrapper').prepend(adContainer);
    
    // After ad completes (e.g., 15 seconds)
    setTimeout(function() {
        adContainer.remove();
        PVH_Player.play();
    }, 15000);
});
```

### Google IMA SDK Integration Example

```javascript
// Enqueue IMA SDK in your addon
PVH_Player.on('ready', function(data) {
    // Initialize IMA
    google.ima.settings.setVpaidMode(google.ima.ImaSdkSettings.VpaidMode.ENABLED);
    
    var adDisplayContainer = new google.ima.AdDisplayContainer(
        document.getElementById('ad-container'),
        document.querySelector('video')
    );
    
    var adsLoader = new google.ima.AdsLoader(adDisplayContainer);
    
    // Request ads
    var adsRequest = new google.ima.AdsRequest();
    adsRequest.adTagUrl = 'YOUR_VAST_TAG_URL';
    adsLoader.requestAds(adsRequest);
});
```

### Milestone Tracking for Analytics

```javascript
PVH_Player.on('milestone', function(data) {
    // Send to Google Analytics
    gtag('event', 'video_progress', {
        'video_id': data.videoId,
        'milestone': data.milestone + '%',
        'duration': data.duration
    });
    
    // Or send to custom analytics
    fetch('/api/analytics/video-progress', {
        method: 'POST',
        body: JSON.stringify({
            video_id: data.videoId,
            progress: data.milestone
        })
    });
});
```

---

## Uploadable Addon Packages

VideoHub's Addons screen can install official addon ZIP packages directly into the core `addons/` directory. This is the packaging contract for official VideoHub addons that customers download from Polanger and upload from **VideoHub -> Addons**.

### Required ZIP Layout

```text
pvh-example-addon.zip
`-- pvh-example-addon/
    |-- pvh-example-addon.php
    |-- pvh-addon.json          # Optional, but recommended
    |-- includes/
    |-- admin/
    `-- public/
```

### Installer Rules

| Rule | Requirement |
|------|-------------|
| File type | Only `.zip` packages are accepted. |
| Root folder | The extracted ZIP must contain exactly one addon folder. Hidden folders and `__MACOSX` are ignored. |
| Slug match | The folder name must match the addon slug from the PHP header or manifest. |
| Main file | The installed addon must include `{addon-slug}.php` at the root of the addon folder. |
| Catalog lock | The slug must be in the official VideoHub addon catalog, unless extended with `pvh_addon_installer_allowed_slugs`. |
| Safety checks | Absolute paths, Windows drive paths, and `..` traversal entries are blocked before extraction. |
| Size limit | The default ZIP size limit is `50 * MB_IN_BYTES` and can be changed with `pvh_addon_installer_max_zip_size`. |

### Optional Manifest

```json
{
  "type": "polanger-videohub-addon",
  "slug": "pvh-example-addon",
  "main": "pvh-example-addon.php",
  "requires_core": "1.8.0"
}
```

### Main File Template

```php
<?php
/**
 * Addon Name: PVH Example Addon
 * Addon URI: https://polanger.com/
 * Description: Adds a custom VideoHub feature.
 * Version: 1.0.0
 * Author: Polanger
 * Author URI: https://polanger.com/
 * Slug: pvh-example-addon
 * Requires: 1.8.0
 * Depends: pvh-auth
 */

if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

add_action( 'polanger_videohub_addons_loaded', function( $addon_manager ) {
    // Register hooks, shortcodes, assets, or service classes here.
} );
```

### Official Installable Slugs

The built-in installer accepts these catalog slugs by default:

```text
pvh-comments, pvh-reactions, pvh-search, pvh-shorts, pvh-auth,
pvh-channels, pvh-uploads, pvh-ads, premium-content, marketplace,
live-streaming, pvh-media-optimizer, pvh-live-chat,
pvh-navigation-panel, pvh-pwa
```

### External Plugin Addons

If an addon must be distributed as a separate WordPress plugin instead of a VideoHub ZIP package, keep using standard WordPress plugin installation and include the VideoHub headers below. These plugins are scanned only when the WordPress plugin is active.

```php
/**
 * Plugin Name: My External VideoHub Addon
 * PVH Addon: true
 * PVH Addon Slug: my-external-addon
 * Requires PVH: 1.8.0
 * PVH Depends: premium-content
 */
```

---

## Complete Hook Inventory

This compact inventory was generated from static `do_action()` and `apply_filters()` calls in the VideoHub core and bundled addons. Dynamic hook families are shown with placeholder names such as `{$tab_slug}`. The source column is a representative call site; some hooks are fired from more than one template.

| Hook | Type | Representative source |
|------|------|-----------------------|
| `polanger_videohub_addon_activated` | action | `includes/class-polanger-videohub-addon-manager.php:569` |
| `polanger_videohub_addon_deactivated` | action | `includes/class-polanger-videohub-addon-manager.php:596` |
| `polanger_videohub_addon_loaded` | action | `includes/class-polanger-videohub-addon-manager.php:481` |
| `polanger_videohub_addon_paths` | filter | `includes/class-polanger-videohub-addon-manager.php:181` |
| `polanger_videohub_addons_loaded` | action | `includes/class-polanger-videohub-addon-manager.php:145` |
| `polanger_videohub_after_video_content` | action | `addons/live-streaming/templates/single-pvh_live_stream.php:272` |
| `polanger_videohub_after_video_details` | action | `addons/live-streaming/templates/single-pvh_live_stream.php:263` |
| `polanger_videohub_allowed_player_html` | filter | `public/class-polanger-videohub-public.php:1202` |
| `polanger_videohub_create_core_addon_tables` | action | `includes/class-polanger-videohub-activator.php:196` |
| `polanger_videohub_database_upgrade` | action | `includes/class-polanger-videohub.php:198` |
| `polanger_videohub_homepage_option_defaults` | filter | `admin/views/settings-homepage-tab.php:43` |
| `polanger_videohub_sanitize_homepage_options` | filter | `admin/class-polanger-videohub-admin.php:623` |
| `polanger_videohub_save_video_meta` | action | `admin/class-polanger-videohub-admin.php:1329` |
| `polanger_videohub_video_actions` | action | `addons/live-streaming/templates/single-pvh_live_stream.php:211` |
| `polanger_videohub_video_details_meta_box` | action | `admin/views/meta-box-video-details.php:110` |
| `polanger_videohub_video_player` | filter | `public/class-polanger-videohub-public.php:1147` |
| `polanger_videohub_video_stats_meta_box` | action | `admin/views/meta-box-video-stats.php:24` |
| `pvh_ad_completed` | action | `addons/pvh-ads/pvh-ads.php:889` |
| `pvh_ad_event_tracked` | action | `addons/pvh-ads/pvh-ads.php:876` |
| `pvh_ad_slot_before_render` | filter | `addons/pvh-ads/pvh-ads.php:512` |
| `pvh_ad_unit_html` | filter | `addons/pvh-ads/pvh-ads.php:550` |
| `pvh_addon_can_activate_{$addon_slug}` | filter | `admin/class-polanger-videohub-admin.php:789` |
| `pvh_addon_installer_allowed_slugs` | filter | `includes/class-polanger-videohub-addon-installer.php:121` |
| `pvh_addon_installer_max_zip_size` | filter | `includes/class-polanger-videohub-addon-installer.php:151` |
| `pvh_admin_dashboard_after` | action | `admin/views/dashboard-page.php:1624` |
| `pvh_ads_should_show` | filter | `addons/pvh-ads/pvh-ads.php:287` |
| `pvh_after_ajax_upload` | action | `addons/pvh-uploads/pvh-uploads.php:1454` |
| `pvh_after_archive_grid` | action | `templates/archive-pvh_video.php:207` |
| `pvh_after_author_profile` | action | `public/views/shortcode-author-profile.php:293` |
| `pvh_after_category_content` | action | `templates/taxonomy-pvh_category.php:250` |
| `pvh_after_category_grid` | action | `templates/taxonomy-pvh_category.php:132` |
| `pvh_after_search_bar` | action | `addons/pvh-search/templates/search-bar.php:60` |
| `pvh_after_search_bar_render` | action | `addons/pvh-search/templates/search-bar.php:338` |
| `pvh_after_shorts_archive` | action | `addons/pvh-shorts/public/views/archive-shorts.php:256` |
| `pvh_after_shorts_feed` | action | `addons/pvh-shorts/public/views/shorts-feed.php:41` |
| `pvh_after_single_playlist` | action | `templates/single-pvh_playlist.php:79` |
| `pvh_after_tag_content` | action | `templates/taxonomy-pvh_tag.php:100` |
| `pvh_archive_grid_after_item` | action | `templates/archive-pvh_video.php:198` |
| `pvh_author_destination_url` | filter | `public/class-polanger-videohub-public.php:1281` |
| `pvh_author_header_actions` | action | `public/views/shortcode-author-profile.php:67` |
| `pvh_author_profile_url` | filter | `public/class-polanger-videohub-public.php:1319` |
| `pvh_before_ajax_upload` | action | `addons/pvh-uploads/pvh-uploads.php:1408` |
| `pvh_before_archive_content` | action | `addons/live-streaming/templates/archive-pvh_live_stream.php:69` |
| `pvh_before_archive_grid` | action | `templates/archive-pvh_video.php:181` |
| `pvh_before_author_profile` | action | `public/views/shortcode-author-profile.php:34` |
| `pvh_before_category_content` | action | `templates/taxonomy-pvh_category.php:78` |
| `pvh_before_category_grid` | action | `templates/taxonomy-pvh_category.php:108` |
| `pvh_before_home_content` | action | `public/views/shortcode-home.php:69` |
| `pvh_before_search_bar_render` | action | `addons/pvh-search/templates/search-bar.php:30` |
| `pvh_before_search_results` | action | `addons/pvh-search/templates/search-results.php:37` |
| `pvh_before_shorts_archive` | action | `addons/pvh-shorts/public/views/archive-shorts.php:146` |
| `pvh_before_shorts_feed` | action | `addons/pvh-shorts/public/views/shorts-feed.php:16` |
| `pvh_before_single_playlist` | action | `templates/single-pvh_playlist.php:35` |
| `pvh_before_single_video` | action | `addons/live-streaming/templates/single-pvh_live_stream.php:118` |
| `pvh_before_tag_content` | action | `templates/taxonomy-pvh_tag.php:47` |
| `pvh_before_videos_content` | action | `public/views/shortcode-videos.php:49` |
| `pvh_can_comment` | filter | `addons/pvh-comments/pvh-comments.php:238` |
| `pvh_can_react` | filter | `addons/pvh-reactions/pvh-reactions.php:791` |
| `pvh_can_upload` | filter | `addons/pvh-uploads/pvh-uploads.php:1003` |
| `pvh_category_grid_after_item` | action | `templates/taxonomy-pvh_category.php:127` |
| `pvh_channel_before_delete` | action | `addons/pvh-channels/pvh-channels.php:1293` |
| `pvh_channel_created` | action | `addons/pvh-channels/pvh-channels.php:1173` |
| `pvh_channel_deleted` | action | `addons/pvh-channels/pvh-channels.php:1304` |
| `pvh_channel_header_actions` | action | `public/views/shortcode-channel.php:38` |
| `pvh_channel_subscribed` | action | `addons/pvh-channels/pvh-channels.php:1448` |
| `pvh_channel_unsubscribed` | action | `addons/pvh-channels/pvh-channels.php:1458` |
| `pvh_channel_updated` | action | `addons/pvh-channels/pvh-channels.php:1252` |
| `pvh_channel_visible_post_statuses` | filter | `addons/pvh-channels/pvh-channels.php:1081` |
| `pvh_chat_can_send` | filter | `addons/pvh-live-chat/pvh-live-chat.php:497` |
| `pvh_chat_message_content` | filter | `addons/pvh-live-chat/includes/class-pvh-live-chat-message.php:564` |
| `pvh_chat_message_deleted` | action | `addons/pvh-live-chat/includes/class-pvh-live-chat-message.php:499` |
| `pvh_chat_message_sent` | action | `addons/pvh-live-chat/includes/class-pvh-live-chat-message.php:148` |
| `pvh_chat_transport` | filter | `addons/pvh-live-chat/pvh-live-chat.php:205` |
| `pvh_comment_added` | action | `addons/pvh-comments/pvh-comments.php:911` |
| `pvh_comment_content` | filter | `addons/pvh-comments/pvh-comments.php:611` |
| `pvh_comment_deleted` | action | `addons/pvh-comments/includes/class-pvh-user-comments.php:474` |
| `pvh_comment_edited` | action | `addons/pvh-comments/pvh-comments.php:1040` |
| `pvh_comment_voted` | action | `addons/pvh-comments/pvh-comments.php:1353` |
| `pvh_dashboard_after_system_status` | action | `admin/views/dashboard-page.php:1156` |
| `pvh_dashboard_monetization_stats` | action | `admin/views/dashboard-page.php:1544` |
| `pvh_dashboard_register_widgets` | action | `admin/views/dashboard-page.php:1615` |
| `pvh_home_after_hero` | action | `public/views/shortcode-home.php:193` |
| `pvh_home_grid_after_item` | action | `public/views/shortcode-home.php:232` |
| `pvh_homepage_settings_after_hero` | action | `admin/views/settings-homepage-tab.php:355` |
| `pvh_live_stream_sidebar_top` | action | `addons/live-streaming/templates/single-pvh_live_stream.php:282` |
| `pvh_marketplace_earning_created` | action | `addons/marketplace/includes/class-pvh-marketplace-earnings.php:145` |
| `pvh_marketplace_earning_gross_amount` | filter | `addons/marketplace/includes/class-pvh-marketplace-woocommerce.php:119` |
| `pvh_marketplace_earning_status_changed` | action | `addons/marketplace/includes/class-pvh-marketplace-earnings.php:292` |
| `pvh_marketplace_earnings_released` | action | `addons/marketplace/includes/class-pvh-marketplace-cron.php:56` |
| `pvh_marketplace_refund_withdrawn_attempt` | action | `addons/marketplace/includes/class-pvh-marketplace-earnings.php:236` |
| `pvh_marketplace_refund_withdrawn_earnings` | action | `addons/marketplace/includes/class-pvh-marketplace-woocommerce.php:306` |
| `pvh_marketplace_vendor_profile_saved` | action | `addons/marketplace/includes/class-pvh-marketplace-vendor-profile.php:212` |
| `pvh_marketplace_withdrawal_created` | action | `addons/marketplace/includes/class-pvh-marketplace-withdrawals.php:194` |
| `pvh_marketplace_withdrawal_paid` | action | `addons/marketplace/includes/class-pvh-marketplace-withdrawals.php:476` |
| `pvh_marketplace_withdrawal_rejected` | action | `addons/marketplace/includes/class-pvh-marketplace-withdrawals.php:636` |
| `pvh_mobile_bar_after_home` | action | `public/class-polanger-videohub-public.php:1574` |
| `pvh_mobile_bar_after_items` | action | `public/class-polanger-videohub-public.php:1589` |
| `pvh_mobile_bar_after_popups` | action | `public/class-polanger-videohub-public.php:1691` |
| `pvh_mobile_bar_after_user` | action | `public/class-polanger-videohub-public.php:1561` |
| `pvh_mobile_bar_before_items` | action | `public/class-polanger-videohub-public.php:1542` |
| `pvh_mobile_bar_before_popups` | action | `public/class-polanger-videohub-public.php:1598` |
| `pvh_mobile_drawer_after_items` | action | `addons/pvh-navigation-panel/templates/mobile-drawer.php:205` |
| `pvh_mobile_drawer_before_items` | action | `addons/pvh-navigation-panel/templates/mobile-drawer.php:40` |
| `pvh_mobile_drawer_footer` | action | `addons/pvh-navigation-panel/templates/mobile-drawer.php:215` |
| `pvh_mobile_user_menu_items` | action | `public/class-polanger-videohub-public.php:1631` |
| `pvh_nav_panel_after_items` | action | `addons/pvh-navigation-panel/templates/navigation-panel.php:75` |
| `pvh_nav_panel_before_items` | action | `addons/pvh-navigation-panel/templates/navigation-panel.php:30` |
| `pvh_nav_panel_footer` | action | `addons/pvh-navigation-panel/templates/navigation-panel.php:260` |
| `pvh_nav_panel_items` | filter | `addons/pvh-navigation-panel/pvh-navigation-panel.php:462` |
| `pvh_playlist_deleted` | action | `includes/class-polanger-videohub.php:678` |
| `pvh_premium_validate_stream_ip` | filter | `addons/premium-content/includes/class-pvh-premium-content-secure-stream.php:303` |
| `pvh_profile_header_actions` | action | `addons/pvh-auth/public/views/profile.php:134` |
| `pvh_profile_tab_content_{$tab_key}` | action | `addons/pvh-auth/public/views/profile.php:511` |
| `pvh_profile_tabs` | filter | `addons/pvh-auth/public/views/profile.php:178` |
| `pvh_reactable_post_types` | filter | `addons/pvh-reactions/pvh-reactions.php:738` |
| `pvh_reaction_added` | action | `addons/pvh-reactions/pvh-reactions.php:458` |
| `pvh_reaction_counts` | filter | `addons/pvh-reactions/pvh-reactions.php:233` |
| `pvh_reaction_removed` | action | `addons/pvh-reactions/pvh-reactions.php:410` |
| `pvh_register_providers` | action | `includes/providers/class-pvh-provider-manager.php:106` |
| `pvh_sanitize_general_options` | filter | `admin/class-polanger-videohub-admin.php:710` |
| `pvh_sanitize_mobile_options` | filter | `admin/class-polanger-videohub-admin.php:728` |
| `pvh_search_after_filter_groups` | action | `addons/pvh-search/templates/search-bar.php:298` |
| `pvh_search_after_type_chips` | action | `addons/pvh-search/templates/search-bar.php:174` |
| `pvh_search_before_filter_groups` | action | `addons/pvh-search/templates/search-bar.php:191` |
| `pvh_search_before_type_chips` | action | `addons/pvh-search/templates/search-bar.php:131` |
| `pvh_search_filter_categories` | filter | `addons/pvh-search/templates/search-bar.php:43` |
| `pvh_search_filter_enabled` | filter | `addons/pvh-search/pvh-search.php:1168` |
| `pvh_search_has_filters` | filter | `addons/pvh-search/templates/search-bar.php:57` |
| `pvh_search_init` | action | `addons/pvh-search/pvh-search.php:141` |
| `pvh_search_query_args` | filter | `addons/pvh-search/pvh-search.php:780` |
| `pvh_search_results` | filter | `addons/pvh-search/pvh-search.php:817` |
| `pvh_search_settings` | filter | `addons/pvh-search/pvh-search.php:1148` |
| `pvh_search_settings_fields` | action | `addons/pvh-search/pvh-search.php:1287` |
| `pvh_search_settings_save` | filter | `addons/pvh-search/pvh-search.php:1329` |
| `pvh_search_settings_saved` | action | `addons/pvh-search/pvh-search.php:1339` |
| `pvh_search_suggestions` | filter | `addons/pvh-search/pvh-search.php:1035` |
| `pvh_search_video_data` | filter | `addons/pvh-search/pvh-search.php:1001` |
| `pvh_settings_general_tab_content` | action | `admin/views/settings-page.php:274` |
| `pvh_settings_mobile_tab_content` | action | `admin/views/settings-page.php:1230` |
| `pvh_settings_save` | action | `admin/class-polanger-videohub-admin.php:821` |
| `pvh_settings_tab_content_{$tab_slug}` | action | `admin/views/settings-page.php:1258` |
| `pvh_settings_tabs` | filter | `admin/views/settings-page.php:102` |
| `pvh_short_data` | filter | `addons/pvh-shorts/pvh-shorts.php:1659` |
| `pvh_short_deleted` | action | `includes/class-polanger-videohub.php:649` |
| `pvh_short_liked` | action | `addons/pvh-shorts/pvh-shorts.php:1605` |
| `pvh_short_viewed` | action | `addons/pvh-shorts/pvh-shorts.php:1569` |
| `pvh_shorts_before_render` | action | `addons/pvh-shorts/pvh-shorts.php:1314` |
| `pvh_shorts_feed_after_item` | action | `addons/pvh-shorts/public/views/shorts-feed.php:34` |
| `pvh_shorts_query_args` | filter | `addons/pvh-shorts/pvh-shorts.php:1300` |
| `pvh_upload_file_types` | filter | `addons/pvh-uploads/pvh-uploads.php:713` |
| `pvh_upload_form_after_details` | action | `addons/pvh-uploads/public/views/upload-edit.php:122` |
| `pvh_upload_max_size` | filter | `addons/pvh-uploads/pvh-uploads.php:695` |
| `pvh_upload_settings` | filter | `addons/pvh-uploads/pvh-uploads.php:961` |
| `pvh_uploads_should_enqueue_assets` | filter | `addons/pvh-uploads/pvh-uploads.php:574` |
| `pvh_user_dropdown_menu_items` | action | `addons/pvh-auth/pvh-auth.php:843` |
| `pvh_video_debug_is_supported_page` | filter | `includes/class-pvh-video-debug.php:651` |
| `pvh_video_debug_page_context` | filter | `includes/class-pvh-video-debug.php:652` |
| `pvh_video_debug_page_report` | filter | `includes/class-pvh-video-debug.php:766` |
| `pvh_video_deleted` | action | `includes/class-polanger-videohub.php:645` |
| `pvh_video_details_saved` | action | `addons/pvh-uploads/pvh-uploads.php:1577` |
| `pvh_video_file_replaced` | action | `addons/pvh-uploads/pvh-uploads.php:1751` |
| `pvh_video_imported` | action | `includes/providers/class-pvh-provider-manager.php:293` |
| `pvh_video_optimized` | action | `addons/pvh-media-optimizer/pvh-media-optimizer.php:1607` |
| `pvh_video_playback_milestone` | action | `addons/pvh-ads/pvh-ads.php:920` |
| `pvh_video_player_args` | filter | `public/class-polanger-videohub-public.php:1038` |
| `pvh_video_player_source` | filter | `public/class-polanger-videohub-public.php:745` |
| `pvh_video_published` | action | `addons/pvh-uploads/pvh-uploads.php:1590` |
| `pvh_video_sidebar_top` | action | `templates/single-pvh_video.php:232` |
| `pvh_video_status_changed` | action | `includes/class-polanger-videohub.php:707` |
| `pvh_video_updated` | action | `admin/class-polanger-videohub-admin.php:1338` |
| `pvh_video_uploaded` | action | `addons/pvh-uploads/pvh-uploads.php:1441` |

---

## Summary

VideoHub provides hooks at every stage of the video lifecycle (upload, import, optimization, publishing) and throughout the UI layer, enabling developers to build powerful integrations and custom addons.

**Key Integration Points:**
- **LMS Integration**: Use `pvh_video_status_changed` to sync with course progress
- **API Integration**: Use lifecycle hooks to sync with external services
- **Cache Management**: Use `pvh_video_updated` and `pvh_video_deleted` to invalidate caches
- **Search Index**: Use lifecycle hooks to update search indexes
- **Analytics**: Use all lifecycle hooks to track user behavior
- **Video Ads**: Use `PVH_Player` JavaScript API for pre-roll, mid-roll, and post-roll ads
- **Engagement Tracking**: Use milestone events for watch time analytics

---

*Documentation Version: 1.9.1 | Last Updated: 2026-06-28*
