Phần 1: Chuẩn bị Camera (Thao tác trên App Imou Life).
Để luồng video có thể đẩy lên Web mượt mà và nét nhất, phần cứng camera phải được cấu hình chuẩn ngay từ đầu.
- Kiểm tra quyền sở hữu gốc: Camera phải được thêm bằng chính tài khoản Imou đang dùng để tạo API. Nếu là camera được “chia sẻ” (Share) từ người khác, API sẽ báo lỗi OP1009 (No right).
- Đổi chuẩn nén video (Cực kỳ quan trọng): Các trình duyệt web hiện tại không hỗ trợ giải mã H.265 qua HLS. Nếu để H.265, máy chủ Imou sẽ ép độ phân giải xuống SD rất mờ.
- Mở app Imou Life > Cài đặt Camera > Cài đặt Video > Chuẩn nén video.
- Đổi từ H.265 sang H.264 để mở khóa luồng HD (streamId = 0) cho Web.
- Lấy mã S/N: Ghi lại mã S/N (Device ID) của mặt sau camera hoặc trong phần thông tin thiết bị trên app.
Phần 2: Khởi tạo API trên Imou Cloud.
- Truy cập nền tảng mở của Imou: Cloud Video And AIoT Services – Imou Open Platform:đăng nhập bằng tài khoản Imou gốc.
- Vào phần Control Board (Bảng điều khiển) -> My App -> Create App để tạo một ứng dụng mới.
- Lấy thông tin xác thực:
- Copy chuỗi AppId.
- Nhấn nút Copy (biểu tượng sao chép) bên cạnh AppSecret để lấy chuỗi 30 ký tự. (Tuyệt đối không dùng chuột bôi đen để tránh bị lỗi, gây lỗi sai chữ ký SN1001).
Phần 3: Cấu hình mã nguồn PHP (Core System).
Tạo một file tên là XEMCAMERA.PHP trên máy chủ web (nếu chạy nội bộ có thể đặt trong thư mục root của IIS trên Windows Server, ở đây mình sẽ đặt lại thư mục wwwroot). Dán toàn bộ mã nguồn chuẩn dưới đây vào file.
Đoạn code này đã bao gồm: Màn hình khóa mật khẩu (tự động reset khi F5), lấy Token 1 lần, lọc luồng HLS HTTPS chuẩn, và giao diện Grid 2×2(4 camera).
Sau khi coppy đoạn mã bên dưới và thay thế thông tin của bạn đầy đủ và lưu lại file có đuôi là PHP và dán file vào mục wwwroot VD mình đặt tên là XEMCAMERA.PHP thì sẽ đc như hình:

Để truy cập được đến Camera này chỉ cần gõ như Sau: https://sangnguyen.drayddns.com/XEMCAMERA.PHP.
Đây là toàn bộ code (Code này dành cho giao diện 4 camera)
<?php
// ĐẶT MẬT KHẨU CỦA BẠN Ở ĐÂY
$password = 'ĐẶT MK KHÓA Ở ĐÂY';
$da_dang_nhap = false;
// Kiểm tra mật khẩu được gửi lên
if (isset($_POST['mat_khau']) && $_POST['mat_khau'] === $password) {
$da_dang_nhap = true;
}
// Nếu sai mật khẩu hoặc load lại trang (không có POST) -> Dừng và bắt nhập lại
if (!$da_dang_nhap) {
echo '
<!DOCTYPE html>
<html lang="vi">
<head>
<meta charset="UTF-8">
<title>Hệ Thống An Ninh</title>
<style>
body { font-family: Arial, sans-serif; background: #111; color: #fff; text-align: center; padding-top: 150px; }
.login-box { background: #222; padding: 30px; border-radius: 8px; border: 2px solid #444; display: inline-block; box-shadow: 0 4px 15px rgba(0,0,0,0.5); }
input { padding: 10px; width: 200px; border-radius: 4px; border: 1px solid #555; background: #333; color: #fff; outline: none; margin-bottom: 15px; }
button { padding: 10px 20px; background: #00bcd4; color: #fff; border: none; border-radius: 4px; cursor: pointer; font-weight: bold; }
button:hover { background: #0097a7; }
</style>
</head>
<body>
<div class="login-box">
<h3 style="margin-top:0; color:#00bcd4;">NHẬP MẬT KHẨU QUẢN TRỊ</h3>
<form method="POST">
<input type="password" name="mat_khau" placeholder="Mật khẩu..." required autofocus><br>
<button type="submit">Truy cập Camera</button>
</form>
</div>
</body>
</html>
';
exit;
}
?>
<!-- Đoạn Script này ép trình duyệt quên mật khẩu ngay sau khi load xong. F5 là khóa lại ngay. -->
<script>
if (window.history.replaceState) {
window.history.replaceState(null, null, window.location.href);
}
</script>
<?php
// =======================================================================
// PHẦN CODE CẤU HÌNH APP ID, APP SECRET VÀ 4 CAMERA CỦA BẠN GIỮ NGUYÊN Ở DƯỚI NÀY
// =======================================================================
$appId = 'NHẬP MÃ APPID CỦA BẠN VÀO ĐÂY';
$appSecret = 'NHẬP MÃ APPSECRET VÀO ĐÂY 30 KÝ TỰ';
// ĐIỀN MÃ S/N VÀ TÊN 4 CAMERA CỦA BẠN VÀO ĐÂY
$cameras = [
[
'id' => 'NHẬP S/N CAM 1',
'name' => 'CAM 1'
],
[
'id' => 'NHẬP S/N CAM 2', // Điền mã cam 2 vào đây (Lưu ý: phải là cam gốc của tài khoản này)
'name' => 'CAM 2'
],
[
'id' => 'NHẬP S/N CAM 3', // Điền mã cam 3 vào đây
'name' => 'CAM 3'
],
[
'id' => 'NHẬP S/N CAM 4', // Điền mã cam 4 vào đây
'name' => 'CAM 4'
]
];
// Hàm tạo Request ID ngẫu nhiên (bắt buộc khi gọi API Imou)
function generateRequestId() {
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));
}
// Hàm giao tiếp với máy chủ Imou (gửi lệnh và nhận kết quả)
function callImouAPI($url, $appId, $appSecret, $params = null) {
$time = time(); // Thời gian hiện tại
$nonce = bin2hex(random_bytes(16)); // Chuỗi ngẫu nhiên chống tấn công lặp lại
$sign = md5("time:$time,nonce:$nonce,appSecret:$appSecret"); // Mã hóa chữ ký điện tử
// Gói dữ liệu gửi đi chuẩn form của Imou
$body = [
'system' => ['ver' => '1.0', 'appId' => $appId, 'sign' => $sign, 'time' => $time, 'nonce' => $nonce],
'id' => generateRequestId(),
'params' => $params === null ? new stdClass() : $params
];
// Mở luồng CURL đẩy dữ liệu lên máy chủ
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json; charset=utf-8']);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_TIMEOUT, 15);
$response = curl_exec($ch);
curl_close($ch);
return json_decode($response, true); // Trả về dạng Mảng (Array) cho dễ xử lý
}
// ==============================================================================
// PHẦN 3: XỬ LÝ LẤY LUỒNG VIDEO
// ==============================================================================
// BƯỚC 3.1: Xin Token (Mã cho phép truy cập) - Chỉ gọi 1 lần để dùng chung
$tokenResponse = callImouAPI('https://openapi.easy4ip.com/openapi/accessToken', $appId, $appSecret);
$token = $tokenResponse['result']['data']['accessToken'] ?? "";
$globalError = $token ? "" : "Lỗi xác thực hệ thống: " . json_encode($tokenResponse);
$camData = []; // Mảng chứa dữ liệu camera để xuất ra ngoài web
// BƯỚC 3.2: Duyệt qua từng camera để xin link phát trực tiếp
foreach ($cameras as $index => $cam) {
$hlsUrl = ""; $camError = ""; $devId = trim($cam['id']);
if (empty($devId)) {
$camError = "Chưa cấu hình mã Camera."; // Báo lỗi nếu để trống mã S/N
} elseif ($token) {
// Gọi lệnh đánh thức camera và lấy luồng proxy
callImouAPI('https://openapi.easy4ip.com/openapi/bindDeviceLive', $appId, $appSecret, [
'token' => $token, 'deviceId' => $devId, 'channelId' => '0', 'streamId' => 0, 'liveMode' => 'proxy'
]);
// Yêu cầu máy chủ Imou trả về đường dẫn Video (Link M3U8)
$streamInfo = callImouAPI('https://openapi.easy4ip.com/openapi/getLiveStreamInfo', $appId, $appSecret, [
'token' => $token, 'deviceId' => $devId, 'channelId' => '0'
]);
// Lọc lấy đúng đường dẫn HLS (HTTPS) luồng chính (streamId = 0)
if (isset($streamInfo['result']['data']['streams'])) {
foreach ($streamInfo['result']['data']['streams'] as $stream) {
if (isset($stream['streamId']) && $stream['streamId'] == 0 && !empty($stream['hls'])) {
$hlsUrl = $stream['hls'];
if (strpos($hlsUrl, 'https://') === 0) break; // Chỉ lấy link bảo mật https
}
}
}
if (empty($hlsUrl)) $camError = "Không lấy được luồng (OP1009).";
}
// Gói dữ liệu từng cam để ném ra cho HTML ở bên dưới
$camData[] = ['index' => $index, 'name' => $cam['name'], 'url' => $hlsUrl, 'error' => $camError];
}
?>
<!-- ============================================================================== -->
<!-- PHẦN 4: GIAO DIỆN HIỂN THỊ WEB (FRONT-END HTML & CSS) -->
<!-- ============================================================================== -->
<!DOCTYPE html>
<html lang="vi">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hệ thống Giám sát Đa màn hình</title>
<!-- Nhúng thư viện HLS.js để phát luồng camera trên web -->
<script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
<style>
/* Cấu hình chung cho toàn trang */
body { font-family: Arial, sans-serif; background: #111; color: #fff; margin: 0; padding: 10px; text-align: center; }
h2 { margin-top: 10px; margin-bottom: 15px; color: #00bcd4; text-transform: uppercase; letter-spacing: 1px; }
/* Cấu hình CSS cho Thanh cảnh báo HEVC */
.hevc-notice {
background: rgba(255, 193, 7, 0.1); border: 1px solid #ffc107; color: #ffca28;
padding: 12px 20px; border-radius: 6px; max-width: 80%; margin: 0 auto 15px auto;
font-size: 14px; display: flex; align-items: center; justify-content: space-between;
text-align: left; box-sizing: border-box;
}
.download-btn {
background: #ffc107; color: #111; text-decoration: none; padding: 8px 15px;
border-radius: 4px; font-weight: bold; white-space: nowrap; margin-left: 15px; transition: 0.2s;
}
.download-btn:hover { background: #e0a800; }
/* Cấu hình bộ khung lưới 4 Camera (2 cột) */
.grid-container { display: grid; grid-template-columns: repeat(2, 1fr); gap: 15px; max-width: 80%; margin: 0 auto; }
.cam-box { background: #222; border: 2px solid #444; border-radius: 8px; position: relative; aspect-ratio: 16/9; overflow: hidden; box-shadow: 0 4px 10px rgba(0,0,0,0.5); }
.cam-name { position: absolute; top: 10px; left: 10px; background: rgba(0,0,0,0.7); color: #fff; padding: 5px 10px; font-size: 14px; font-weight: bold; border-radius: 4px; z-index: 10; }
video { width: 100%; height: 100%; object-fit: cover; background: #000; }
.error-msg { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); color: #ff4444; font-size: 14px; width: 80%; }
/* Responsive: Tự động gom thành 1 cột khi xem trên Điện thoại */
@media (max-width: 768px) {
.hevc-notice { flex-direction: column; text-align: center; gap: 10px; }
.download-btn { margin-left: 0; }
.grid-container { grid-template-columns: 1fr; max-width: 95%; }
}
</style>
</head>
<body>
<h2>XEM CAMERA NHÀ ÔNG 6 DŨNG HIHI </h2>
<!-- KHUNG THÔNG BÁO TẢI CODEC NẰM DƯỚI TIÊU ĐỀ -->
<div class="hevc-notice">
<span>⚠️ <strong>Trường hợp Màn hình đen & Xoay vòng:</strong> Máy tính của bạn đang thiếu bộ giải mã video chuẩn nén cao (H.265). Vui lòng tải và cài đặt Codec để xem hình ảnh sắc nét nhất.</span>
<a href="https://sangnguyen.drayddns.com/wp-content/uploads/tai-lieu-bi-kip/LINH-TINH/HEVCVideoExtension.rar" class="download-btn">⬇️ Tải Codec Khắc Phục</a>
</div>
<!-- NẾU LỖI TOKEN TỔNG: In ra màu đỏ, NẾU KHÔNG: Hiện 4 màn hình -->
<?php if ($globalError): ?>
<div style="color: red; padding: 20px;"><?php echo $globalError; ?></div>
<?php else: ?>
<div class="grid-container">
<?php foreach ($camData as $c): ?>
<div class="cam-box">
<div class="cam-name"><?php echo htmlspecialchars($c['name']); ?></div> <!-- In tên Cam -->
<?php if ($c['url']): ?>
<!-- NẾU CÓ LINK: Hiện thẻ Video -->
<video id="video-<?php echo $c['index']; ?>" controls autoplay muted playsinline></video>
<?php else: ?>
<!-- NẾU LỖI (Ví dụ: sai mã, mất mạng cam): Báo dòng chữ đỏ -->
<div class="error-msg"><?php echo $c['error']; ?></div>
<?php endif; ?>
</div>
<?php endforeach; ?>
</div>
<!-- ============================================================================== -->
<!-- PHẦN 5: TRÌNH PHÁT VIDEO JAVASCRIPT (ÉP CHẠY VÀ CỨU HỘ) -->
<!-- ============================================================================== -->
<script>
// Nhận dữ liệu link camera từ khối PHP ở trên ném xuống JS
const cameras = <?php echo json_encode($camData); ?>;
cameras.forEach(cam => {
if (cam.url) { // Nếu cam này có link mới thực thi tiếp
const video = document.getElementById('video-' + cam.index);
// Ép các thuộc tính tắt tiếng để lách quy định cấm Autoplay của trình duyệt
video.muted = true; video.defaultMuted = true; video.playsInline = true;
if (Hls.isSupported()) {
// Khởi tạo thư viện giải mã HLS (Live Stream)
const hls = new Hls({ enableWorker: true, lowLatencyMode: true, liveSyncDurationCount: 3 });
hls.loadSource(cam.url);
hls.attachMedia(video);
// Khi đã nạp thành công dữ liệu đầu tiên -> Bấm nút Play
hls.on(Hls.Events.MANIFEST_PARSED, () => {
let playPromise = video.play();
if (playPromise !== undefined) {
playPromise.catch(() => {
video.muted = true; video.play(); // Thử Play lại lần 2 nếu bị chặn
});
}
});
// LẮNG NGHE LỖI: Tự động cứu hộ (Khôi phục) nếu video bị gián đoạn mạng
hls.on(Hls.Events.ERROR, function (event, data) {
if (data.fatal) {
switch (data.type) {
case Hls.ErrorTypes.NETWORK_ERROR:
hls.startLoad(); // Cứu rớt mạng -> Tải lại
break;
case Hls.ErrorTypes.MEDIA_ERROR:
hls.recoverMediaError(); // Cứu lỗi hình -> Xóa bộ nhớ đệm và khôi phục
break;
default:
hls.destroy(); // Hỏng nặng -> Hủy phiên
break;
}
}
});
} else if (video.canPlayType('application/vnd.apple.mpegurl')) {
// Hỗ trợ riêng cho người dùng xem trên máy Mac / iPhone / Safari
video.src = cam.url;
video.addEventListener('loadedmetadata', () => { video.muted = true; video.play(); });
}
}
});
</script>
<?php endif; ?>
</body>
</html>
