https://github.com/streamlink/streamlink
https://github.com/streamlink/windows-builds/releases
https://github.com/DDVTECH/mistserver
====================================================================================
tạo file server.sh để chuyển tiếp từ Youtube sang Restreamer
------------------------------------------------------------------------------------
while true; do
youtube_url=$(cat 1.txt) # Đọc URL từ tệp
streamlink "$youtube_url" best --stdout | ffmpeg -re -i - -c:v libx264 -preset veryfast -r 30 -g 60 -c:a aac -b:a 128k -ar 44100 -f flv "rtmp://27.74.246.165/1/1"
sleep 10
done
===================================================================================
cài streamlink
-----------------------------------------------------------------------------------
sudo apt update
sudo apt install streamlink
sudo apt install ffmpeg
nano link.txt
nano stream_youtube.sh
-----------------------------------------------------------------------------------
TẠO FILE stream_youtube.sh
-----------------------------------------------------------------------------------
#!/bin/bash
URL_FILE="link.txt"
RTMP_URL="rtmp://27.74.246.165:1940/trungdungmedia/0902577267"
while true; do
if [[ -f "$URL_FILE" ]]; then
STREAM_URL=$(<"$URL_FILE")
if [[ -n "$STREAM_URL" ]]; then
echo "Đang phát từ: $STREAM_URL"
streamlink "$STREAM_URL" best --stdout | ffmpeg -re -i - -c:v copy -c:a copy -f flv "$RTMP_URL"
else
echo "File URL trống. Đợi 10 giây..."
sleep 10
fi
else
echo "Không tìm thấy file $URL_FILE. Đợi 10 giây..."
sleep 10
fi
sleep 10
done
====================================================================================
tạo API để nhập link qua trình duyệt
------------------------------------------------------------------------------------
sudo apt update
sudo apt install python3-pip -y
pip3 install flask
------------------------------------------------------------------------------------
Tạo Flask App (start_server_API.py)
------------------------------------------------------------------------------------
from flask import Flask, request, render_template_string, jsonify
import os
import subprocess # Để chạy lệnh shell
app = Flask(__name__)
stream_info = {}
URL_FILE = "/home/sv01/restream/restream_rtmp/rtmp_link.txt"
###################################################################
HTML = """
<!doctype html>
<title>MÁY CHỦ RESTREAM RTMP</title>
<style>
body {
font-family: Arial, sans-serif;
padding: 20px;
background-color: #f4f4f4;
margin-bottom: 40px; /* For space for the watermark */
}
/* Phần form chứa nút */
.form-container {
background-color: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
margin-bottom: 20px;
text-align: center;
}
/* Nút Bật Restream */
.restream-button {
background-color: #28a745; /* Màu nền xanh */
color: white;
padding: 10px 20px;
border: none;
border-radius: 10px; /* Bo tròn góc */
font-size: 16px;
font-weight: bold;
cursor: pointer;
transition: all 0.3s ease; /* Hiệu ứng mượt khi rê chuột */
}
/* Khi rê chuột vào nút */
.restream-button:hover {
background-color: #218838; /* Thay đổi màu nền khi rê chuột */
transform: scale(1.1); /* Tăng kích thước nút khi rê chuột */
}
/* Khi nút được nhấn */
.restream-button:active {
background-color: #1e7e34; /* Màu nền khi nhấn */
transform: scale(1); /* Đưa về kích thước ban đầu khi nhấn */
}
/* Footer watermark */
.footer {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
background-color: rgba(0, 0, 0, 0.5);
color: white;
text-align: center;
padding: 10px 0;
font-size: 14px;
font-family: Arial, sans-serif;
}
</style>
<!---------------------------------------------------------------------------------------------------------------------------------->
<body>
<h2 style="margin-bottom: 5px;">MÁY CHỦ RESTREAM RTMP</h2>
<br><p>Bạn đang dùng mạng 4g để livestream, đường mạng bạn yếu quá không thể stream lên các nền tảng như youtube, facebook, rtmp . . .
<p> Hãy yên tâm đã có máy chủ Restream giúp bạn
<!---------------------------------------------------------------------------------------------------------------------------------->
<h3 style="background-color: red; color: white; padding: 5px 10px; border-radius: 5px; display: inline-block; margin-top: 0;">
<p>
Bước 1. nhập vào stream Vmix hoặc OBS => rtmp://27.74.246.165:1955/trungdungmedia | streamkey: 0902577267 | nhấn Start
</p>
Bước 2. Lấy luồng livestream và nhập vào ô bên dưới | <a href="https://youtu.be/T8cIsB-UNuk?si=WxVlLT3UPpTKjgq8" target="_blank" rel="noopener noreferrer">
Xem hướng dẫn
</a>
</p>
<p>
Bước 3. nhấn nút Bật Restream để Livestream | xem Thông tin luồng đang chạy có Bitrate là Restream đã chạy .
</h3></p>
<br>
<!---------------------------------------------------------------------------------------------------------------------------------->
<!-- Thêm nút Nhập link RTMP-->
<form method="post">
<textarea name="urls" placeholder="Nhập nhiều link RTMP, mỗi link một dòng" required style="resize: both; width: 100%; min-height: 100px;"></textarea><br>
<input type="submit" class="restream-button" value="Nhập Link . . .">
</form>
<!---------------------------------------------------------------------------------------------------------------------------------->
<br>
<!-- Thêm nút bật/tắt cho Restream -->
<form method="post" action="/start_restream">
<!-- Nút Bật Restream -->
<input type="submit" class="restream-button" value="Bật Restream" />
</form>
</body>
<!-- Hiển thị thông báo -->
<p>{{ message|safe }}</p>
{% if saved_links %}
<h3>🔄 Link đang restream:</h3>
<ul>
{% for link in saved_links %}
<li>{{ link }}</li>
{% endfor %}
</ul>
{% endif %}
<h3>📋 Thông tin luồng đang chạy:</h3>
<ul>
{% for key, value in stream_info.items() %}
<li><strong>{{ key }}</strong>: {{ value }}</li>
{% endfor %}
</ul>
<!-- Đoạn mã JavaScript để tự động chuyển hướng sau 10 giây -->
{% if message %}
<script>
setTimeout(function() {
window.location.href = '/'; // Quay lại trang chủ
}, 10000); // Sau 10 giây
</script>
{% endif %}
<!-- Tự động làm mới trang mỗi 15 giây -->
<script>
setTimeout(function() {
window.location.reload();
}, 15000); // 15000 milliseconds = 15 seconds
</script>
<!------------------------------------------------------------>
<!-- Footer watermark -->
<div class="footer">
@ thiết kế by trungdungmedia.com 2025
</div>
"""
####################################################################################
@app.route("/", methods=["GET", "POST"])
def update_url():
message = ""
saved_links = []
if os.path.exists(URL_FILE):
with open(URL_FILE, "r") as f:
saved_links = [line.strip() for line in f if line.strip()]
if request.method == "POST":
raw_urls = request.form.get("urls", "").strip()
url_list = [line.strip() for line in raw_urls.splitlines() if line.strip()]
if url_list:
try:
with open(URL_FILE, "w") as f:
for url in url_list:
f.write(url + "\n")
saved_links = url_list
message = f"""
<div style="background-color: #d4edda; color: #155724; border: 1px solid #c3e6cb; padding: 10px; border-radius: 5px;">
✅ {len(saved_links)} link đã được nhập thành công.
</div>
"""
except Exception as e:
message = f"<div style='color:red;'>❌ Không thể ghi file: {e}</div>"
# Trả về HTML với message, saved_links và stream_info
return render_template_string(HTML, message=message, saved_links=saved_links, stream_info=stream_info)
####################################################################################
@app.route("/start_restream", methods=["POST"])
def toggle_restream():
try:
# Chạy lệnh ./restream_rtmp.sh trong nền
subprocess.Popen(["/bin/bash", "/home/sv01/restream/restream_rtmp/restream_rtmp.sh"])
message = """
<div style="background-color: #d4edda; color: #155724; border: 1px solid #c3e6cb; padding: 10px; border-radius: 5px;">
✅ Restream đã được bật thành công.
</div>
"""
except Exception as e:
message = f"<div style='color:red;'>❌ Không thể thực thi lệnh: {e}</div>"
# Truyền stream_info cùng với các biến khác vào render_template_string
return render_template_string(HTML, message=message, saved_links=[], stream_info=stream_info)
####################################################################################
@app.route('/update_stream_info', methods=['POST'])
def update_stream_info():
print("[DEBUG] Received a POST request to /update_stream_info")
data = request.get_json(force=True)
fps = data.get("fps")
bitrate = data.get("bitrate")
resolution = data.get("resolution")
print(f"FPS: {fps} | Bitrate: {bitrate} | Resolution: {resolution}")
# Thêm dòng này để cập nhật stream_info hiển thị ra HTML
stream_info["FPS"] = fps
stream_info["Bitrate"] = bitrate
stream_info["Resolution"] = resolution
return jsonify({"status": "success"})
####################################################################################
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5484)
------------------------------------------------------------------------------------
Chạy server
python3 start_server_API.py
====================================================================================
tạo file restream_rtmp.sh
-----------------------------------------------------------------------------------
#!/bin/bash
# === CẤU HÌNH ===
INPUT="rtmp://27.74.246.165:1955/trungdungmedia/0902577267"
RTMP_FILE="/home/sv01/restream/restream_rtmp/rtmp_link.txt"
FLASK_URL="http://192.168.14.165:5484/update_stream_info"
MAX_RETRIES=5
RETRY_COUNT=0
FFMPEG_PIDS=()
# === ĐỌC URL RTMP TỪ FILE ===
read_streams_from_file() {
if [[ -f "$RTMP_FILE" ]]; then
mapfile -t STREAMS < "$RTMP_FILE"
else
echo "[ERROR] Không tìm thấy file $RTMP_FILE"
exit 1
fi
if [[ ${#STREAMS[@]} -eq 0 ]]; then
echo "[ERROR] Tệp $RTMP_FILE không chứa URL RTMP hợp lệ!"
exit 1
fi
}
# === KIỂM TRA NGUỒN STREAM TRƯỚC KHI CHẠY ===
check_input() {
ffprobe -v error -i "$INPUT" -show_streams > /dev/null
return $?
}
# === KIỂM TRA URL RTMP ĐẦU RA ===
check_output_stream() {
local STREAM="$1"
if [[ ! "$STREAM" =~ ^srt:// && ! "$STREAM" =~ ^rtmp:// && ! "$STREAM" =~ ^rtmps:// ]]; then
echo "[ERROR] URL không hợp lệ: $STREAM"
return 1
fi
return 0
}
# === GỬI THÔNG TIN LÊN FLASK ===
send_to_flask() {
local fps=$1
local bitrate=$2
local resolution=$3
curl -s -X POST -H "Content-Type: application/json" \
-d "{\"fps\": \"$fps\", \"bitrate\": \"$bitrate\", \"resolution\": \"$resolution\"}" \
"$FLASK_URL" > /dev/null
}
# === BẮT ĐẦU RESTREAM ===
start_restream() {
echo "[INFO] ✅ Bắt đầu restream đa luồng từ nguồn: $INPUT"
START_TIME=$(date "+%H:%M:%S")
echo "[INFO] Thời gian bắt đầu: $START_TIME"
for STREAM in "${STREAMS[@]}"; do
check_output_stream "$STREAM" || continue
LOG_FILE="/tmp/ffmpeg_$(basename "$STREAM" | tr '/' '_').log"
echo "[INFO] ▶️ Đang phát tới $STREAM (log: $LOG_FILE)"
# Chạy ffmpeg và ghi stderr vào file log riêng
if [[ "$STREAM" =~ ^srt:// ]]; then
ffmpeg -re -i "$INPUT" -c:v copy -c:a copy -f mpegts "$STREAM" 2> "$LOG_FILE" &
else
ffmpeg -re -i "$INPUT" -c:v copy -c:a copy -f flv "$STREAM" 2> "$LOG_FILE" &
fi
FFMPEG_PID=$!
FFMPEG_PIDS+=($FFMPEG_PID)
echo "[INFO] FFmpeg PID $FFMPEG_PID phụ trách $STREAM"
# Giám sát stream này bằng một tiến trình nền
(
while kill -0 "$FFMPEG_PID" 2>/dev/null; do
STREAM_INFO=$(ffprobe -v quiet -print_format json -show_streams "$INPUT")
fps=$(echo "$STREAM_INFO" | jq -r '.streams[0].r_frame_rate' | awk -F'/' '{ if ($2==0) print 0; else printf "%.2f", $1/$2 }')
width=$(echo "$STREAM_INFO" | jq -r '.streams[0].width // 0')
height=$(echo "$STREAM_INFO" | jq -r '.streams[0].height // 0')
resolution="${width}x${height}"
# Lấy bitrate thực tế từ log (dòng gần nhất có "bitrate=")
bitrate=$(grep -oP 'bitrate=\s*\K[0-9.]+' "$LOG_FILE" | tail -n1)
[[ -z "$bitrate" ]] && bitrate=0
echo "[INFO] [$STREAM] FPS: $fps | Bitrate: $bitrate | Resolution: $resolution"
send_to_flask "$fps" "$bitrate" "$resolution"
sleep 5
done
) &
done
# Đợi tất cả tiến trình ffmpeg kết thúc
for PID in "${FFMPEG_PIDS[@]}"; do
wait $PID
done
}
# === CHẠY CHÍNH ===
read_streams_from_file
while true; do
check_input
if [ $? -ne 0 ]; then
echo "[ERROR] ❌ Không kết nối được nguồn RTMP!"
echo "[INFO] Đang thử lại sau 5 giây..."
sleep 5
((RETRY_COUNT++))
if [ $RETRY_COUNT -ge $MAX_RETRIES ]; then
echo "[ERROR] 🛑 Không thể kết nối sau $MAX_RETRIES lần thử. Dừng script."
exit 1
fi
else
start_restream
echo "[INFO] 🔁 Quay lại kiểm tra nguồn RTMP..."
fi
done
====================================================================================
chạy trong môi trường công cộng
-----------------------------------------------------------------------------------
pip install --upgrade click
pip install --upgrade flask
python3 -m venv venv
source venv/bin/activate
pip install flask click gunicorn
sudo apt-get install python3-click python3-flask
------------------------------------------------------------------------------------
chạy server
gunicorn -w 4 -b 0.0.0.0:5484 start_server_API:app
====================================================================================
====================================================================================
tạo restream_youtube.sh
------------------------------------------------------------------------------------
#!/bin/bash
# Định nghĩa các file URL
URL_FILE="/home/sv01/restream/restream_youtube/youtube_link.txt"
RTMP_URL_FILE="/home/sv01/restream/restream_youtube/may_chu_link.txt"
# Biến đếm số lần kiểm tra
max_retries=10
retries=0
while true; do
# Kiểm tra file URL cho stream
if [[ -f "$URL_FILE" ]]; then
STREAM_URL=$(<"$URL_FILE")
if [[ -n "$STREAM_URL" ]]; then
echo "Đang phát từ: $STREAM_URL"
else
echo "File URL trống. Đợi 10 giây..."
sleep 10
retries=$((retries+1))
if [ $retries -ge $max_retries ]; then
echo "Không có URL đầu vào sau 10 lần kiểm tra. Dừng script."
break
fi
continue
fi
else
echo "Không tìm thấy file $URL_FILE. Đợi 10 giây..."
sleep 10
retries=$((retries+1))
if [ $retries -ge $max_retries ]; then
echo "Không có file URL sau 10 lần kiểm tra. Dừng script."
break
fi
continue
fi
# Kiểm tra file RTMP URL
if [[ -f "$RTMP_URL_FILE" ]]; then
RTMP_URL=$(<"$RTMP_URL_FILE")
if [[ -n "$RTMP_URL" ]]; then
echo "Đang phát lên RTMP: $RTMP_URL"
else
echo "File RTMP URL trống. Đợi 10 giây..."
sleep 10
retries=$((retries+1))
if [ $retries -ge $max_retries ]; then
echo "Không có URL đầu ra sau 10 lần kiểm tra. Dừng script."
break
fi
continue
fi
else
echo "Không tìm thấy file $RTMP_URL_FILE. Đợi 10 giây..."
sleep 10
retries=$((retries+1))
if [ $retries -ge $max_retries ]; then
echo "Không có file RTMP URL sau 10 lần kiểm tra. Dừng script."
break
fi
continue
fi
# Chạy streamlink và ffmpeg để phát trực tiếp
streamlink "$STREAM_URL" best --stdout | ffmpeg -re -i - -c:v copy -c:a copy -f flv "$RTMP_URL"
# Đặt lại số lần kiểm tra sau mỗi lần phát
retries=0
# Đợi 10 giây trước khi kiểm tra lại
sleep 10
done