diff --git a/m3u8-player/frontend/package-lock.json b/m3u8-player/frontend/package-lock.json index e591159..2350547 100644 --- a/m3u8-player/frontend/package-lock.json +++ b/m3u8-player/frontend/package-lock.json @@ -17,6 +17,7 @@ "hls.js": "^1.5.15", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-router-dom": "^6.26.2", "react-scripts": "^5.0.1", "web-vitals": "^2.1.4" }, @@ -3448,6 +3449,14 @@ } } }, + "node_modules/@remix-run/router": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.19.2.tgz", + "integrity": "sha512-baiMx18+IMuD1yyvOGaHM9QrVUPGGG0jC+z+IPHnRJWUAUvaKuWKyE8gjDj2rzv3sz9zOGoRSPgeBVHRhZnBlA==", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@rollup/plugin-babel": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", @@ -14474,6 +14483,36 @@ "node": ">=0.10.0" } }, + "node_modules/react-router": { + "version": "6.26.2", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.26.2.tgz", + "integrity": "sha512-tvN1iuT03kHgOFnLPfLJ8V95eijteveqdOSk+srqfePtQvqCExB8eHOYnlilbOcyJyKnYkr1vJvf7YqotAJu1A==", + "dependencies": { + "@remix-run/router": "1.19.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.26.2", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.26.2.tgz", + "integrity": "sha512-z7YkaEW0Dy35T3/QKPYB1LjMK2R1fxnHO8kWpUMTBdfVzZrWOiY9a7CtN8HqdWtDUWd5FY6Dl8HFsqVwH4uOtQ==", + "dependencies": { + "@remix-run/router": "1.19.2", + "react-router": "6.26.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, "node_modules/react-scripts": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz", @@ -20060,6 +20099,11 @@ "source-map": "^0.7.3" } }, + "@remix-run/router": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.19.2.tgz", + "integrity": "sha512-baiMx18+IMuD1yyvOGaHM9QrVUPGGG0jC+z+IPHnRJWUAUvaKuWKyE8gjDj2rzv3sz9zOGoRSPgeBVHRhZnBlA==" + }, "@rollup/plugin-babel": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", @@ -28008,6 +28052,23 @@ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", "integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==" }, + "react-router": { + "version": "6.26.2", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.26.2.tgz", + "integrity": "sha512-tvN1iuT03kHgOFnLPfLJ8V95eijteveqdOSk+srqfePtQvqCExB8eHOYnlilbOcyJyKnYkr1vJvf7YqotAJu1A==", + "requires": { + "@remix-run/router": "1.19.2" + } + }, + "react-router-dom": { + "version": "6.26.2", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.26.2.tgz", + "integrity": "sha512-z7YkaEW0Dy35T3/QKPYB1LjMK2R1fxnHO8kWpUMTBdfVzZrWOiY9a7CtN8HqdWtDUWd5FY6Dl8HFsqVwH4uOtQ==", + "requires": { + "@remix-run/router": "1.19.2", + "react-router": "6.26.2" + } + }, "react-scripts": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz", diff --git a/m3u8-player/frontend/package.json b/m3u8-player/frontend/package.json index 84508f9..4d09003 100644 --- a/m3u8-player/frontend/package.json +++ b/m3u8-player/frontend/package.json @@ -3,6 +3,7 @@ "version": "0.1.0", "private": true, "dependencies": { + "@heroicons/react": "^2.0.18", "@tailwindcss/aspect-ratio": "^0.4.2", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^13.4.0", @@ -11,9 +12,9 @@ "hls.js": "^1.5.15", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-router-dom": "^6.26.2", "react-scripts": "^5.0.1", - "web-vitals": "^2.1.4", - "@heroicons/react": "^2.0.18" + "web-vitals": "^2.1.4" }, "scripts": { "start": "npx react-scripts start", diff --git a/m3u8-player/frontend/src/App.js b/m3u8-player/frontend/src/App.js index 95ac4f4..e58144e 100644 --- a/m3u8-player/frontend/src/App.js +++ b/m3u8-player/frontend/src/App.js @@ -1,252 +1,31 @@ -import React, { useState, useEffect } from 'react'; -import VideoPlayer from './components/VideoPlayer'; -import { ChevronLeftIcon, ChevronRightIcon, PencilIcon, TrashIcon, PlayIcon } from '@heroicons/react/24/solid'; +import React from 'react'; +import { BrowserRouter as Router, Route, Routes, Link } from 'react-router-dom'; +import LandingPage from './components/LandingPage'; +import MainApp from './components/MainApp'; // 确保这里的路径正确 function App() { - const [customChannels, setCustomChannels] = useState([]); - const [selectedChannel, setSelectedChannel] = useState(null); - const [error, setError] = useState(null); - const [isSidebarOpen, setIsSidebarOpen] = useState(true); - const [isUploadMode, setIsUploadMode] = useState(false); - const [jsonInput, setJsonInput] = useState(''); - const [editingChannel, setEditingChannel] = useState(null); - const [urlInput, setUrlInput] = useState(''); - - useEffect(() => { - const storedChannels = JSON.parse(localStorage.getItem('userChannels')) || []; - setCustomChannels(storedChannels); - - const urlParams = new URLSearchParams(window.location.search); - const playUrl = urlParams.get('play'); - if (playUrl) { - setSelectedChannel({ name: 'URL Playback', url: playUrl }); - setUrlInput(playUrl); - } - }, []); - - const handleChannelSelect = (channel) => { - setSelectedChannel(channel); - setIsUploadMode(false); - setUrlInput(channel.url); - const newUrl = `${window.location.pathname}?play=${encodeURIComponent(channel.url)}`; - window.history.pushState({ url: channel.url }, '', newUrl); - }; - - const handleUrlSubmit = (e) => { - e.preventDefault(); - if (urlInput) { - setSelectedChannel({ name: 'Custom URL', url: urlInput }); - const newUrl = `${window.location.pathname}?play=${encodeURIComponent(urlInput)}`; - window.history.pushState({ url: urlInput }, '', newUrl); - } - }; - - const handleJsonSubmit = () => { - try { - const json = JSON.parse(jsonInput); - if (Array.isArray(json) && json.every(item => item.name && item.url)) { - const newChannels = json.map((item, index) => ({ - id: `custom-${customChannels.length + index + 1}`, - name: item.name, - url: item.url - })); - const updatedChannels = [...customChannels, ...newChannels]; - setCustomChannels(updatedChannels); - localStorage.setItem('userChannels', JSON.stringify(updatedChannels)); - setJsonInput(''); - setError(null); - } else { - setError('JSON 格式不正确。请确保文件包含 name 和 url 字段的对象数组。'); - } - } catch (error) { - setError('无法解析 JSON。请检查格式。'); - } - }; - - const toggleSidebar = () => { - setIsSidebarOpen(!isSidebarOpen); - }; - - const toggleUploadMode = () => { - setIsUploadMode(!isUploadMode); - setSelectedChannel(null); - setEditingChannel(null); - }; - - const handleEditChannel = (channel) => { - setEditingChannel(channel); - }; - - const handleUpdateChannel = () => { - const updatedChannels = customChannels.map(ch => - ch.id === editingChannel.id ? editingChannel : ch - ); - setCustomChannels(updatedChannels); - localStorage.setItem('userChannels', JSON.stringify(updatedChannels)); - setEditingChannel(null); - }; - - const handleDeleteChannel = (channelId) => { - const updatedChannels = customChannels.filter(ch => ch.id !== channelId); - setCustomChannels(updatedChannels); - localStorage.setItem('userChannels', JSON.stringify(updatedChannels)); - }; - return ( -
{error}
} - {isUploadMode ? ( -
- 请在下方文本框中粘贴包含频道信息的 JSON 内容。JSON 应包含一个对象数组,每个对象有两个键:
- name (频道名称) 和
- url (流媒体 URL)。
-
- {JSON.stringify([
- { name: "示例频道 1", url: "http://example.com/stream1.m3u8" },
- { name: "示例频道 2", url: "http://example.com/stream2.m3u8" }
- ], null, 2)}
-
- {error}
} + {isUploadMode ? ( +
+ 请在下方文本框中粘贴包含频道信息的 JSON 内容。JSON 应包含一个对象数组,每个对象有两个键:
+ name (频道名称) 和
+ url (流媒体 URL)。
+
+ {JSON.stringify([
+ { name: "示例频道 1", url: "http://example.com/stream1.m3u8" },
+ { name: "示例频道 2", url: "http://example.com/stream2.m3u8" }
+ ], null, 2)}
+
+ 请从左侧选择一个频道或输入 M3U8 URL
+ )} +