支持:响应式 / 循环播放 / 签名 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;
}
评论区
发表新的留言
您可以留言提出您的疑问或建议。
您的留言得到回复时,会通过您填写的邮箱提醒您。