- Implemented.
Safari/iPhone recording now uses MP4/AAC when WebM is unavailable. WebM remains the default when supported and FFmpeg is disabled. Added optional FFmpeg profiles: recommended M4A/AAC-LC or requested MP4/H.264 + AAC. Added MIME-aware, playsinline audio playback. Added dashboard controls under Plans & Platform → Voice Transcoding / FFmpeg. Added Apache/Nginx media MIME configuration and documentation. Core implementation: voice_transcoder.php (line 72), messages.php (line 86), and cyberchat-app.js (line 635).
This commit is contained in:
+28
-5
@@ -1,6 +1,7 @@
|
||||
<?php
|
||||
define('CYBERCHAT_API', true);
|
||||
require_once __DIR__ . '/../bootstrap.php';
|
||||
require_once ROOT_DIR . '/lib/voice_transcoder.php';
|
||||
sendCorsHeaders();
|
||||
|
||||
try { archiveYesterdayIfNeeded(); } catch (Throwable $e) { error_log($e->getMessage()); }
|
||||
@@ -83,26 +84,48 @@ function sendVoiceMessage(): never {
|
||||
}
|
||||
|
||||
$mime = class_exists('finfo') ? (string)(new finfo(FILEINFO_MIME_TYPE))->file($file['tmp_name']) : '';
|
||||
$header = (string)file_get_contents($file['tmp_name'], false, null, 0, 12);
|
||||
$header = (string)file_get_contents($file['tmp_name'], false, null, 0, 16);
|
||||
if (str_starts_with($header, "\x1A\x45\xDF\xA3")) $mime = 'audio/webm';
|
||||
elseif (str_starts_with($header, 'RIFF') && substr($header, 8, 4) === 'WAVE') $mime = 'audio/wav';
|
||||
elseif (strlen($header) >= 8 && substr($header, 4, 4) === 'ftyp') $mime = 'audio/mp4';
|
||||
elseif (strlen($header) >= 2 && ord($header[0]) === 0xFF && (ord($header[1]) & 0xF0) === 0xF0) $mime = 'audio/aac';
|
||||
$allowed = [
|
||||
'audio/webm' => 'webm',
|
||||
'video/webm' => 'webm',
|
||||
'audio/wav' => 'wav',
|
||||
'audio/x-wav' => 'wav',
|
||||
'audio/wave' => 'wav',
|
||||
'audio/mp4' => 'm4a',
|
||||
'video/mp4' => 'm4a',
|
||||
'audio/aac' => 'aac',
|
||||
'audio/x-aac' => 'aac',
|
||||
];
|
||||
if (!isset($allowed[$mime])) jsonResponse(['error' => 'Voice clips must be WAV or WebM'], 400);
|
||||
if (!isset($allowed[$mime])) jsonResponse(['error' => 'Voice clips must be WebM, MP4/M4A, AAC, or WAV'], 400);
|
||||
|
||||
$dir = voiceUploadDir();
|
||||
ensureWritableDirectory($dir, 'Voice storage');
|
||||
$filename = bin2hex(random_bytes(20)) . '.' . $allowed[$mime];
|
||||
if (!move_uploaded_file($file['tmp_name'], $dir . '/' . $filename)) {
|
||||
$token = bin2hex(random_bytes(20));
|
||||
$sourceFilename = $token . '.' . $allowed[$mime];
|
||||
$sourcePath = $dir . '/' . $sourceFilename;
|
||||
if (!move_uploaded_file($file['tmp_name'], $sourcePath)) {
|
||||
jsonResponse(['error' => 'Could not store voice clip'], 500);
|
||||
}
|
||||
|
||||
$storedMime = $mime === 'video/webm' ? 'audio/webm' : $mime;
|
||||
$sourceMime = match ($mime) {
|
||||
'video/webm' => 'audio/webm',
|
||||
'video/mp4' => 'audio/mp4',
|
||||
'audio/x-wav', 'audio/wave' => 'audio/wav',
|
||||
'audio/x-aac' => 'audio/aac',
|
||||
default => $mime,
|
||||
};
|
||||
try {
|
||||
$stored = transcodeVoiceRecording($sourcePath, $sourceFilename, $sourceMime);
|
||||
} catch (Throwable $e) {
|
||||
if (is_file($sourcePath)) @unlink($sourcePath);
|
||||
throw $e;
|
||||
}
|
||||
$filename = $stored['filename'];
|
||||
$storedMime = $stored['mime'];
|
||||
$db = getDB();
|
||||
try {
|
||||
$db->prepare("INSERT INTO messages
|
||||
|
||||
Reference in New Issue
Block a user