支持:响应式 / 循环播放 / 签名 URL 自动刷新 / 可选 autoplay / 前台可用 ajaxUrl / nonce 防刷

使用示例:

[aliyun_vod video_id="xxxxxxxx" width="1920" height="1080" autoplay="1"]

使用方法:

将代码引入到functions.php中。在wp-config.php中设置:

define('ALIYUN_VOD_ACCESS_KEY_ID', 'LTAIxxxxxx');
define('ALIYUN_VOD_ACCESS_KEY_SECRET', 'yourSecretKeyHere');

源码:

<?php
/**
 * 阿里云 VOD 短代码插件(无 SDK 依赖)
 * 支持:响应式 / 循环播放 / 签名 URL 自动刷新 / 可选 autoplay / 前台可用 ajaxUrl / nonce 防刷
 *
 * 用法示例:
 * [aliyun_vod video_id="xxxxxxxx" width="1920" height="1080" autoplay="1"]
 *
 * 建议把密钥放 wp-config.php:
 * define('ALIYUN_VOD_ACCESS_KEY_ID', 'LTAIxxxxxx');
 * define('ALIYUN_VOD_ACCESS_KEY_SECRET', 'yourSecretKeyHere');
 */

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

add_shortcode('aliyun_vod', 'aliyun_vod_shortcode');

function aliyun_vod_shortcode($atts) {
    aliyun_vod_enqueue_assets();
    $atts = shortcode_atts([
        'video_id' => '',
        'width' => '1920',
        'height' => '1080',
        'autoplay' => '0',
        'loop' => '1',
        'muted' => '1',
        'controls' => '0',
        'objectfit' => 'contain'
    ], $atts);
    if (empty($atts['video_id'])) return '<p style="color:red;">错误:缺少 video_id 参数</p>';
    $w = (int) $atts['width'];
    $h = (int) $atts['height'];
    if ($w <= 0 || $h <= 0) { $w = 1920; $h = 1080; }
    $gcd = function ($a, $b) use (&$gcd) { return $b ? $gcd($b, $a % $b) : $a; };
    $divisor = $gcd($w, $h);
    $ratio_w = $w / $divisor;
    $ratio_h = $h / $divisor;
    static $counter = 0;
    $vid = 'aliyun-vod-' . (++$counter);
    $autoplay = aliyun_vod_truthy($atts['autoplay']);
    $loop = aliyun_vod_truthy($atts['loop']);
    $muted = aliyun_vod_truthy($atts['muted']);
    $controls = aliyun_vod_truthy($atts['controls']);
    $objectfit = in_array($atts['objectfit'], ['contain', 'cover', 'fill', 'none', 'scale-down'], true) ? $atts['objectfit'] : 'contain';
    $video_attrs = [];
    $video_attrs[] = 'playsinline';
    $video_attrs[] = 'preload="metadata"';
    if ($muted) $video_attrs[] = 'muted';
    if ($loop) $video_attrs[] = 'loop';
    if ($controls) $video_attrs[] = 'controls';
    if ($autoplay) $video_attrs[] = 'autoplay';
    $video_attrs_str = implode(' ', $video_attrs);
    return sprintf(
        '<div class="aliyun-vod-responsive" style="aspect-ratio:%d/%d;width:100%%;max-width:%dpx;">
            <div class="aliyun-vod-wrapper" data-video-id="%s" data-vid="%s" data-autoplay="%d">
                <video id="%s" style="width:100%%;height:100%%;object-fit:%s;" %s>
                    <source src="" type="video/mp4">
                </video>
            </div>
        </div>',
        (int) $ratio_w,
        (int) $ratio_h,
        (int) $w,
        esc_attr($atts['video_id']),
        esc_attr($vid),
        $autoplay ? 1 : 0,
        esc_attr($vid),
        esc_attr($objectfit),
        $video_attrs_str
    );
}

function aliyun_vod_truthy($v) {
    if (is_bool($v)) return $v;
    $v = strtolower(trim((string) $v));
    return in_array($v, ['1', 'true', 'yes', 'y', 'on'], true);
}

function aliyun_vod_enqueue_assets() {
    static $done = false;
    if ($done) return;
    $done = true;
    $css = '.aliyun-vod-responsive { position: relative; background: #000; margin: 1em 0; overflow: hidden; border-radius: 6px; } .aliyun-vod-responsive video { display: block; background: #000; }';
    wp_add_inline_style('wp-block-library', $css);
    wp_register_script('aliyun-vod-manager', '', [], '1.1', true);
    wp_enqueue_script('aliyun-vod-manager');
    $boot = 'window.ALIYUN_VOD = ' . wp_json_encode([
        'ajaxUrl' => admin_url('admin-ajax.php'),
        'nonce' => wp_create_nonce('aliyun_vod_nonce'),
    ]) . ';';
    wp_add_inline_script('aliyun-vod-manager', $boot, 'before');
    $js = <<<JS
(function() {
    const videos = new Map();
    let globalTimer = null;
    const REFRESH_INTERVAL = 30000;
    const TIMEOUT_BUFFER = 10000;
    const AUTH_TIMEOUT_MS = 120000;
    function init() {
        document.querySelectorAll('.aliyun-vod-wrapper').forEach(wrapper => {
            const videoId = wrapper.dataset.videoId;
            const vid = wrapper.dataset.vid;
            const autoplay = wrapper.dataset.autoplay === '1';
            const videoEl = wrapper.querySelector('video');
            if (!videoEl || !videoId || !vid) return;
            videos.set(vid, {
                element: videoEl,
                videoId: videoId,
                autoplay: autoplay,
                urlExpiresAt: 0,
                isLoading: false
            });
            loadVideoUrl(vid);
        });
        if (!globalTimer && videos.size > 0) {
            globalTimer = setInterval(checkAndRefresh, REFRESH_INTERVAL);
        }
    }
    async function loadVideoUrl(vid) {
        const info = videos.get(vid);
        if (!info || info.isLoading) return;
        info.isLoading = true;
        const wasPlaying = !info.element.paused && !info.element.ended;
        const currentTime = info.element.currentTime || 0;
        try {
            const formData = new URLSearchParams();
            formData.append('action', 'get_aliyun_vod_url');
            formData.append('video_id', info.videoId);
            formData.append('_ajax_nonce', window.ALIYUN_VOD?.nonce || '');
            const response = await fetch(window.ALIYUN_VOD?.ajaxUrl || '', {
                method: 'POST',
                body: formData,
                credentials: 'same-origin'
            });
            const data = await response.json();
            if (data && data.success && data.data) {
                const newUrl = data.data;
                if (info.element.src !== newUrl) {
                    info.element.src = newUrl;
                    info.element.addEventListener('loadedmetadata', function onMeta() {
                        info.element.removeEventListener('loadedmetadata', onMeta);
                        try {
                            if (currentTime > 0 && isFinite(currentTime)) {
                                info.element.currentTime = Math.min(currentTime, info.element.duration || currentTime);
                            }
                        } catch(e) {}
                        if (info.autoplay || wasPlaying) {
                            info.element.play().catch(() => {});
                        }
                    }, { once: true });
                } else {
                    if (info.autoplay) info.element.play().catch(() => {});
                }
                info.urlExpiresAt = Date.now() + AUTH_TIMEOUT_MS - TIMEOUT_BUFFER;
            } else {
            }
        } catch (e) {
            console.error('Aliyun VOD load failed:', e);
        } finally {
            info.isLoading = false;
        }
    }
    function checkAndRefresh() {
        const now = Date.now();
        for (const [vid, info] of videos) {
            if (now >= info.urlExpiresAt && isInViewport(info.element)) {
                loadVideoUrl(vid);
            }
        }
    }
    function isInViewport(el) {
        const rect = el.getBoundingClientRect();
        return (
            rect.top >= -100 &&
            rect.left >= -100 &&
            rect.bottom <= (window.innerHeight + 100) &&
            rect.right <= (window.innerWidth + 100)
        );
    }
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }
    window.addEventListener('beforeunload', () => {
        if (globalTimer) clearInterval(globalTimer);
    });
})();
JS;
    wp_add_inline_script('aliyun-vod-manager', $js, 'after');
}

add_action('wp_ajax_get_aliyun_vod_url', 'aliyun_vod_ajax_handler');
add_action('wp_ajax_nopriv_get_aliyun_vod_url', 'aliyun_vod_ajax_handler');

function aliyun_vod_ajax_handler() {
    $nonce = $_POST['_ajax_nonce'] ?? '';
    if (!wp_verify_nonce($nonce, 'aliyun_vod_nonce')) {
        wp_send_json_error('Invalid nonce', 403);
    }
    $video_id = sanitize_text_field($_POST['video_id'] ?? '');
    if (!$video_id) {
        wp_send_json_error('Missing video_id', 400);
    }
    $ip = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
    $throttle_key = 'aliyun_vod_throttle_' . md5($ip . '|' . $video_id);
    if (get_transient($throttle_key)) {
        wp_send_json_error('Too many requests', 429);
    }
    set_transient($throttle_key, 1, 3);
    $access_key_id = defined('ALIYUN_VOD_ACCESS_KEY_ID') ? ALIYUN_VOD_ACCESS_KEY_ID : '';
    $access_key_secret = defined('ALIYUN_VOD_ACCESS_KEY_SECRET') ? ALIYUN_VOD_ACCESS_KEY_SECRET : '';
    if (!$access_key_id || !$access_key_secret) {
        wp_send_json_error('Aliyun credentials not configured', 500);
    }
    $play_url = get_aliyun_vod_play_url($video_id, $access_key_id, $access_key_secret, 'cn-shanghai');
    if ($play_url) {
        wp_send_json_success($play_url);
    } else {
        wp_send_json_error('Failed to get play URL', 500);
    }
}

function get_aliyun_vod_play_url($video_id, $access_key_id, $access_key_secret, $region_id = 'cn-shanghai') {
    $params = [
        'Action' => 'GetPlayInfo',
        'Version' => '2017-03-21',
        'VideoId' => $video_id,
        'AuthInfoTimeout' => 120,
        'Format' => 'JSON',
        'Timestamp' => gmdate('Y-m-d\TH:i:s\Z'),
        'SignatureMethod' => 'HMAC-SHA1',
        'SignatureVersion' => '1.0',
        'SignatureNonce' => uniqid(mt_rand(), true),
        'AccessKeyId' => $access_key_id,
    ];
    ksort($params);
    $canonical = '';
    foreach ($params as $key => $value) {
        $canonical .= '&' . rawurlencode($key) . '=' . rawurlencode($value);
    }
    $canonical = ltrim($canonical, '&');
    $string_to_sign = "GET&%2F&" . rawurlencode($canonical);
    $signature = base64_encode(hash_hmac('sha1', $string_to_sign, $access_key_secret . '&', true));
    $params['Signature'] = $signature;
    $query = http_build_query($params, '', '&', PHP_QUERY_RFC3986);
    $url = 'https://vod.' . $region_id . '.aliyuncs.com/?' . $query;
    $response = wp_remote_get($url, ['timeout' => 15]);
    if (is_wp_error($response)) {
        error_log('Aliyun VOD API Error: ' . $response->get_error_message());
        return false;
    }
    $body = wp_remote_retrieve_body($response);
    $data = json_decode($body, true);
    if (!is_array($data)) {
        error_log('Aliyun VOD Response JSON decode failed: ' . substr($body, 0, 300));
        return false;
    }
    $playInfos = $data['PlayInfoList']['PlayInfo'] ?? [];
    if (!is_array($playInfos) || empty($playInfos)) {
        error_log('Aliyun VOD Response Error: ' . print_r($data, true));
        return false;
    }
    $mp4Url = null;
    foreach ($playInfos as $pi) {
        $format = strtolower($pi['Format'] ?? '');
        if ($format === 'mp4' && !empty($pi['PlayURL'])) {
            $mp4Url = $pi['PlayURL'];
            break;
        }
    }
    if ($mp4Url) return $mp4Url;
    if (!empty($playInfos[0]['PlayURL'])) return $playInfos[0]['PlayURL'];
    error_log('Aliyun VOD Response Error: ' . print_r($data, true));
    return false;
}