iOS 11 个播客应用程序在大约 20 秒时开始播客
iOS 11 podcast app starts podcasts over at about 20 second mark
在 iOS 11 更新后,我客户的所有播客都在 iTunes 应用程序中从大约二十到四十秒开始播放。 iOS10播放器不显示此问题。没有其他播放器证明此问题,并且一旦情节重新开始,它就会毫无问题地播放到最后。在任何给定的剧集中,重新开始都保持在同一点,但我怀疑它发生在更长的剧集中更远的地方,好像它可能处于持续时间的某个百分比。
包括拖动播放头在内的 FF 和 RW 功能均有效。
无论是否允许 iTunes 播客应用程序下载剧集文件,iTunes 播客应用程序都会重新启动。
几周来我们一直未能成功获得 Apple 支持,我想我也可以在这里发布。
Apple 支持人员检查了 mp3 文件和 RSS 提要,没有发现任何问题。
<enclosure url="http://example.com/audio/episodes/2018_04_18_1458_some-show.mp3" length="63773956" type="audio/mpeg"/>
<pubDate>Wed, 18 Apr 2018 00:00:00 -0500</pubDate>
<itunes:duration>1:06:25</itunes:duration>
客户的生死取决于 Google 分析统计信息,因此我们在 htaccess 中使用 mod_rewrite 路由 MP3 的入站请求,就像这样
RewriteCond %{HTTP_REFERER} !^http://(www\.)?example\.com [NC]
RewriteRule ^audio/episodes/([^/\.]+).mp3$ /audio/episodes/google_analytics_mp3.php?mp3=&redirected=1 [L,QSA]
并且测试表明,删除路由后,以前未播放的剧集中不会出现该问题。之前播放过的剧集继续出现这个问题。
google_analytics_mp3.php:
<?php
include_once($_SERVER['DOCUMENT_ROOT']."/classDBI/classDBI.php");
include_once($_SERVER['DOCUMENT_ROOT']."/classDBI/google_analytics_api.php");
$e = new Episode;
if (is_numeric($_REQUEST['mp3'])) {
$id = intval($_REQUEST['mp3']);
list($ep) = $e->retrieve("id = $id");
if ($ep) {
$outcome = ga_send_pageview(basename($ep->audio_link), 'Site streaming: ' . $ep->google_title());
}
} else if ($_REQUEST['redirected'] == 1) {
$fileName = $_GET['mp3'] . '.mp3';
$fileLocation = $_SERVER['DOCUMENT_ROOT'].'/audio/episodes/'.$fileName;
$etag = md5_file($fileName);
$fileRedirect = '/audio/episodes/'.$fileName;
if (file_exists($fileLocation)) {
list($ep) = $e->retrieve("audio_link = 'audio/episodes/$fileName'");
$pageName = 'Direct access';
if ($ep) {
$pageName .= ': ' . $ep->google_title();
}
$size = filesize($fileLocation);
$time = date('r', filemtime($fileLocation));
$fm = @fopen($fileLocation, 'rb');
if (!$fm) {
header ("HTTP/1.1 505 Internal server error");
return;
}
$begin = 0;
$end = $size - 1;
if (isset($_SERVER['HTTP_RANGE'])) {
if (preg_match('/bytes=\h*(\d+)-(\d*)[\D.*]?/i', $_SERVER['HTTP_RANGE'], $matches)) {
$begin = intval($matches[1]);
if (!empty($matches[2])) {
$end = intval($matches[2]);
}
}
}
if (isset($_SERVER['HTTP_RANGE'])) {
header('HTTP/1.1 206 Partial Content');
if ($begin == 0) {
$outcome = ga_send_pageview($fileName, 'Streaming: ' . $pageName);
}
} else{
header('HTTP/1.1 200 OK');
$outcome = ga_send_pageview($fileName, $pageName);
}
header("Content-Transfer-Encoding: binary");
header('Content-Type: audio/mpeg');
header("Etag: $etag");
header('Cache-Control: public, must-revalidate, max-age=0');
header('Pragma: no-cache');
header('Accept-Ranges: bytes');
header('Content-Length:' . (($end - $begin) + 1));
header('Content-Disposition: inline; filename="'.$fileName.'"');
// 2018.01.14 changes
if (isset($_SERVER['HTTP_RANGE'])) {
header("Content-Range: bytes $begin-$end/$size");
}
// END 2018.01.14 changes
readfile($fileLocation);
header("Last-Modified: $time");
$cur = $begin;
fseek($fm, $begin, 0);
while(!feof($fm) && $cur <= $end && (connection_status() == 0)) {
print fread($fm, min(1024 * 16, ($end - $cur) + 1));
$cur += 1024 * 16;
}
exit();
}
}
我非常确定 headers 的处理方式导致了重启问题,但我找不到错误。
以下是 headers 来自 Chrome 开发小组的 mp3 请求。 IP 地址更改为文件名:
General
Request URL: http://example.com/audio/episodes/2018_04_18_1458_some-show.mp3
Request Method: GET
Status Code: 200 OK
Remote Address: 205.196.xxx.xxx:80
Referrer Policy: no-referrer-when-downgrade
Response Headers
Accept-Ranges: bytes
Cache-Control: public, must-revalidate, max-age=0
Connection: Keep-Alive
Content-Disposition: inline; filename="2018_04_18_1458_some-show.mp3"
Content-Length: 63773956
Content-Transfer-Encoding: binary
Content-Type: audio/mpeg
Date: Fri, 20 Apr 2018 16:27:57 GMT
Etag: 7fe7b0375cd99ec4d928b1a8885bee81
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Keep-Alive: timeout=2, max=100
Pragma: no-cache
Server: Apache
Request Headers
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Cache-Control: no-cache
Connection: keep-alive
Cookie: PHPSESSID=TYDy9IZXJwTCBlmXVmkHn0; _ga=GA1.2.904913110.1515511103; _gid=GA1.2.666994959.1523989189; __unam=739f578-160db803df3-1cf2ee83-70
Host: www.example.com
Pragma: no-cache
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36
如果这里有帮助的话 google_analytics_api.php:
<?php
define('GOOGLEACCOUNT', 'UA-xxxxxxxx-1');
define('GOOGLEDOMAIN', $_SERVER['HTTP_HOST']);
function gaParseCookie() {
if (isset($_COOKIE['_ga'])) {
list($version, $domainDepth, $cid1, $cid2) = explode('.', $_COOKIE["_ga"], 4);
$contents = array('version' => $version, 'domainDepth' => $domainDepth, 'cid' => $cid1 . '.' . $cid2);
$cid = $contents['cid'];
} else {
$cid = gaGenerateUUID();
}
return $cid;
}
function gaGenerateUUID() {
return sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
mt_rand(0, 0xffff), mt_rand(0, 0xffff),
mt_rand(0, 0xffff),
mt_rand(0, 0x0fff) | 0x4000,
mt_rand(0, 0x3fff) | 0x8000,
mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
);
}
function gaSendData($data) {
$getString = 'https://ssl.google-analytics.com/collect';
$getString .= '?payload_data&';
$getString .= http_build_query($data);
$options = array(
CURLOPT_CUSTOMREQUEST =>"GET",
CURLOPT_POST =>false,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_ENCODING => "",
CURLOPT_CONNECTTIMEOUT => 120,
CURLOPT_TIMEOUT => 120,
CURLOPT_MAXREDIRS => 10,
);
$ch = curl_init( $getString );
curl_setopt_array( $ch, $options );
$result = curl_exec( $ch );
$err = curl_errno( $ch );
$errmsg = curl_error( $ch );
$header = curl_getinfo( $ch );
if ($err) {
mail('email@example.com', 'Analytics MP3 Error' . __FILE__, "$err\n\n$errmsg\n\n$header");
}
curl_close( $ch );
return $result;
}
function ga_send_pageview($file, $title) {
$data = array(
'v' => 1,
'tid' => GOOGLEACCOUNT,
'cid' => gaParseCookie(),
't' => 'pageview',
'dh' => GOOGLEDOMAIN,
'dp' => $file,
'dt' => $title
);
if(strlen($_SERVER['HTTP_REFERER'])) {
$data['utmr'] = $_SERVER['HTTP_REFERER'];
}
if (strlen($_SERVER['HTTP_USER_AGENT'])) {
$data['ua'] = $_SERVER['HTTP_USER_AGENT'];
}
gaSendData($data);
}
function ga_send_event($category=null, $action=null, $label=null) {
$data = array(
'v' => 1,
'tid' => GOOGLEACCOUNT,
'cid' => gaParseCookie(),
't' => 'event',
'ec' => $category, //Category (Required)
'ea' => $action, //Action (Required)
'el' => $label
);
gaSendData($data);
}
非常感谢任何建议!
问题原来是播客应用程序似乎已将其在 HTTP_RANGE 初始请求结束时发送的内容更改为 1,如下所示:
[HTTP_RANGE] => bytes=0-1
此代码正在解析 header
if (isset($_SERVER['HTTP_RANGE'])) {
if (preg_match('/bytes=\h*(\d+)-(\d*)[\D.*]?/i', $_SERVER['HTTP_RANGE'], $matches)) {
$begin = intval($matches[1]);
if (!empty($matches[2])) {
$end = intval($matches[2]);
}
}
}
结果出站 Content-Range header 看起来像 0-1/59829252
这会立即导致第二次请求重新启动播客。
已通过将 1 的结束值替换为 $size - 1 来修复。
在 iOS 11 更新后,我客户的所有播客都在 iTunes 应用程序中从大约二十到四十秒开始播放。 iOS10播放器不显示此问题。没有其他播放器证明此问题,并且一旦情节重新开始,它就会毫无问题地播放到最后。在任何给定的剧集中,重新开始都保持在同一点,但我怀疑它发生在更长的剧集中更远的地方,好像它可能处于持续时间的某个百分比。
包括拖动播放头在内的 FF 和 RW 功能均有效。
无论是否允许 iTunes 播客应用程序下载剧集文件,iTunes 播客应用程序都会重新启动。
几周来我们一直未能成功获得 Apple 支持,我想我也可以在这里发布。
Apple 支持人员检查了 mp3 文件和 RSS 提要,没有发现任何问题。
<enclosure url="http://example.com/audio/episodes/2018_04_18_1458_some-show.mp3" length="63773956" type="audio/mpeg"/>
<pubDate>Wed, 18 Apr 2018 00:00:00 -0500</pubDate>
<itunes:duration>1:06:25</itunes:duration>
客户的生死取决于 Google 分析统计信息,因此我们在 htaccess 中使用 mod_rewrite 路由 MP3 的入站请求,就像这样
RewriteCond %{HTTP_REFERER} !^http://(www\.)?example\.com [NC]
RewriteRule ^audio/episodes/([^/\.]+).mp3$ /audio/episodes/google_analytics_mp3.php?mp3=&redirected=1 [L,QSA]
并且测试表明,删除路由后,以前未播放的剧集中不会出现该问题。之前播放过的剧集继续出现这个问题。
google_analytics_mp3.php:
<?php
include_once($_SERVER['DOCUMENT_ROOT']."/classDBI/classDBI.php");
include_once($_SERVER['DOCUMENT_ROOT']."/classDBI/google_analytics_api.php");
$e = new Episode;
if (is_numeric($_REQUEST['mp3'])) {
$id = intval($_REQUEST['mp3']);
list($ep) = $e->retrieve("id = $id");
if ($ep) {
$outcome = ga_send_pageview(basename($ep->audio_link), 'Site streaming: ' . $ep->google_title());
}
} else if ($_REQUEST['redirected'] == 1) {
$fileName = $_GET['mp3'] . '.mp3';
$fileLocation = $_SERVER['DOCUMENT_ROOT'].'/audio/episodes/'.$fileName;
$etag = md5_file($fileName);
$fileRedirect = '/audio/episodes/'.$fileName;
if (file_exists($fileLocation)) {
list($ep) = $e->retrieve("audio_link = 'audio/episodes/$fileName'");
$pageName = 'Direct access';
if ($ep) {
$pageName .= ': ' . $ep->google_title();
}
$size = filesize($fileLocation);
$time = date('r', filemtime($fileLocation));
$fm = @fopen($fileLocation, 'rb');
if (!$fm) {
header ("HTTP/1.1 505 Internal server error");
return;
}
$begin = 0;
$end = $size - 1;
if (isset($_SERVER['HTTP_RANGE'])) {
if (preg_match('/bytes=\h*(\d+)-(\d*)[\D.*]?/i', $_SERVER['HTTP_RANGE'], $matches)) {
$begin = intval($matches[1]);
if (!empty($matches[2])) {
$end = intval($matches[2]);
}
}
}
if (isset($_SERVER['HTTP_RANGE'])) {
header('HTTP/1.1 206 Partial Content');
if ($begin == 0) {
$outcome = ga_send_pageview($fileName, 'Streaming: ' . $pageName);
}
} else{
header('HTTP/1.1 200 OK');
$outcome = ga_send_pageview($fileName, $pageName);
}
header("Content-Transfer-Encoding: binary");
header('Content-Type: audio/mpeg');
header("Etag: $etag");
header('Cache-Control: public, must-revalidate, max-age=0');
header('Pragma: no-cache');
header('Accept-Ranges: bytes');
header('Content-Length:' . (($end - $begin) + 1));
header('Content-Disposition: inline; filename="'.$fileName.'"');
// 2018.01.14 changes
if (isset($_SERVER['HTTP_RANGE'])) {
header("Content-Range: bytes $begin-$end/$size");
}
// END 2018.01.14 changes
readfile($fileLocation);
header("Last-Modified: $time");
$cur = $begin;
fseek($fm, $begin, 0);
while(!feof($fm) && $cur <= $end && (connection_status() == 0)) {
print fread($fm, min(1024 * 16, ($end - $cur) + 1));
$cur += 1024 * 16;
}
exit();
}
}
我非常确定 headers 的处理方式导致了重启问题,但我找不到错误。
以下是 headers 来自 Chrome 开发小组的 mp3 请求。 IP 地址更改为文件名:
General
Request URL: http://example.com/audio/episodes/2018_04_18_1458_some-show.mp3
Request Method: GET
Status Code: 200 OK
Remote Address: 205.196.xxx.xxx:80
Referrer Policy: no-referrer-when-downgrade
Response Headers
Accept-Ranges: bytes
Cache-Control: public, must-revalidate, max-age=0
Connection: Keep-Alive
Content-Disposition: inline; filename="2018_04_18_1458_some-show.mp3"
Content-Length: 63773956
Content-Transfer-Encoding: binary
Content-Type: audio/mpeg
Date: Fri, 20 Apr 2018 16:27:57 GMT
Etag: 7fe7b0375cd99ec4d928b1a8885bee81
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Keep-Alive: timeout=2, max=100
Pragma: no-cache
Server: Apache
Request Headers
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Cache-Control: no-cache
Connection: keep-alive
Cookie: PHPSESSID=TYDy9IZXJwTCBlmXVmkHn0; _ga=GA1.2.904913110.1515511103; _gid=GA1.2.666994959.1523989189; __unam=739f578-160db803df3-1cf2ee83-70
Host: www.example.com
Pragma: no-cache
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36
如果这里有帮助的话 google_analytics_api.php:
<?php
define('GOOGLEACCOUNT', 'UA-xxxxxxxx-1');
define('GOOGLEDOMAIN', $_SERVER['HTTP_HOST']);
function gaParseCookie() {
if (isset($_COOKIE['_ga'])) {
list($version, $domainDepth, $cid1, $cid2) = explode('.', $_COOKIE["_ga"], 4);
$contents = array('version' => $version, 'domainDepth' => $domainDepth, 'cid' => $cid1 . '.' . $cid2);
$cid = $contents['cid'];
} else {
$cid = gaGenerateUUID();
}
return $cid;
}
function gaGenerateUUID() {
return sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
mt_rand(0, 0xffff), mt_rand(0, 0xffff),
mt_rand(0, 0xffff),
mt_rand(0, 0x0fff) | 0x4000,
mt_rand(0, 0x3fff) | 0x8000,
mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
);
}
function gaSendData($data) {
$getString = 'https://ssl.google-analytics.com/collect';
$getString .= '?payload_data&';
$getString .= http_build_query($data);
$options = array(
CURLOPT_CUSTOMREQUEST =>"GET",
CURLOPT_POST =>false,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_ENCODING => "",
CURLOPT_CONNECTTIMEOUT => 120,
CURLOPT_TIMEOUT => 120,
CURLOPT_MAXREDIRS => 10,
);
$ch = curl_init( $getString );
curl_setopt_array( $ch, $options );
$result = curl_exec( $ch );
$err = curl_errno( $ch );
$errmsg = curl_error( $ch );
$header = curl_getinfo( $ch );
if ($err) {
mail('email@example.com', 'Analytics MP3 Error' . __FILE__, "$err\n\n$errmsg\n\n$header");
}
curl_close( $ch );
return $result;
}
function ga_send_pageview($file, $title) {
$data = array(
'v' => 1,
'tid' => GOOGLEACCOUNT,
'cid' => gaParseCookie(),
't' => 'pageview',
'dh' => GOOGLEDOMAIN,
'dp' => $file,
'dt' => $title
);
if(strlen($_SERVER['HTTP_REFERER'])) {
$data['utmr'] = $_SERVER['HTTP_REFERER'];
}
if (strlen($_SERVER['HTTP_USER_AGENT'])) {
$data['ua'] = $_SERVER['HTTP_USER_AGENT'];
}
gaSendData($data);
}
function ga_send_event($category=null, $action=null, $label=null) {
$data = array(
'v' => 1,
'tid' => GOOGLEACCOUNT,
'cid' => gaParseCookie(),
't' => 'event',
'ec' => $category, //Category (Required)
'ea' => $action, //Action (Required)
'el' => $label
);
gaSendData($data);
}
非常感谢任何建议!
问题原来是播客应用程序似乎已将其在 HTTP_RANGE 初始请求结束时发送的内容更改为 1,如下所示:
[HTTP_RANGE] => bytes=0-1
此代码正在解析 header
if (isset($_SERVER['HTTP_RANGE'])) {
if (preg_match('/bytes=\h*(\d+)-(\d*)[\D.*]?/i', $_SERVER['HTTP_RANGE'], $matches)) {
$begin = intval($matches[1]);
if (!empty($matches[2])) {
$end = intval($matches[2]);
}
}
}
结果出站 Content-Range header 看起来像 0-1/59829252
这会立即导致第二次请求重新启动播客。
已通过将 1 的结束值替换为 $size - 1 来修复。