本来是想练手golang的,但一看服务器已经是PHP的形状了,懒癌发作了,PHP不也挺好吗(
PHP视频播放平台搭建实践
项目预览链接
http://mxzfun.com/videoAll/public/index.php
完整项目程序
见如下Github链接:
https://github.com/yosoroQ/videoAll/tree/main
项目展示
PC端
移动端
项目引言
项目背景
- mxzfun.com是我的日常博客,有些文章我会放些视频上去用以记录,但他们都是以单体的形式出现,你要去“刷”视频,实属不方便。
- 所以想出构建一个独立页面用于读取和播放服务器里所上传的视频。
技术选型
- PHP + 模板渲染 + 原生 JS + CSS Grid
- 没啥好说的,其实就是WordPress他用的是PHP,我也懒得再去服务器里新建个其他程序框架的项目了。
技术架构概览
项目目录结构
videoALL/
│
├─ public/ # 网站公开访问目录
│ ├─ index.php # 入口文件,路由和模板调用
│ └─ assets/ # 静态资源:CSS、JS
├─ script.js
└─ style.css
│
├─ app/ # 核心应用逻辑
│ ├─ Video.php # 视频扫描、过滤、排序、分页
│ └─ Category.php # 分类目录扫描、按钮生成
│
├─ templates/ # HTML 模板
│ ├─ header.php # 公共头部
│ ├─ footer.php # 公共底部
│ └─ video_grid.php# 视频网格渲染
│
├─ config/ # 配置文件
│ └─ config.php # 上传目录、排除目录、分页数量、支持格式
│
└─ logs/ # 可选日志目录核心组件
视频扫描模块(Video.php)
- 扫描
uploads/目录及子目录 - 过滤视频格式(mp4、webm、mov 等)
- 处理中文和空格文件名(URL 编码)
- 根据修改时间排序,实现最新视频优先显示
- 分页切片,支持每页显示固定数量(如 16 个)
分类模块(Category.php)
- 读取
uploads/下一级子目录 - 排除特定目录(如
Aspose.Words、ppress_uploads、sea) - 自动生成分类按钮,点击切换显示对应分类视频
分页与网格渲染(video_grid.php + style.css + script.js)
- 网格渲染视频块,显示视频标题
- CSS
grid+aspect-ratio保证比例统一 - JS 实现悬停播放,提升交互体验
- 分页链接展示当前页码和总页数,支持快速跳转
项目目录设计
public/ —— 网站入口与静态资源
用途:
- 公开访问目录,浏览器访问的根路径
- 放置入口文件
index.php - 静态资源文件:CSS、JS、图片等
- 视频上传目录
uploads/,存放 WordPress 的视频文件
设计理念:
- 保证前端和用户可直接访问的文件集中管理
- 将核心业务逻辑和模板分离,避免用户直接访问敏感 PHP 逻辑
具体结构:
public/ ├─ index.php # 网站入口 ├─ uploads/ # 视频文件存放 │ ├─ 2020/ │ ├─ 2021/ │ └─ ... └─ assets/ # 静态资源 ├─ style.css └─ script.js
app/ —— 核心业务逻辑
用途:
- 处理网站的核心功能,包括视频扫描、分类生成、分页计算等
核心文件:
Video.php:- 扫描指定目录及子目录的视频文件
- 过滤支持格式(mp4、mov、webm 等)
- 对中文或空格文件名进行 URL 编码处理
- 根据修改时间排序,实现最新视频优先显示
- 切片分页数据供模板渲染
Category.php:- 读取一级子目录生成分类列表
- 排除特定目录(如
Aspose.Words、ppress_uploads、sea) - 输出分类按钮 HTML 或数组供模板调用
设计理念:
- 模块化:核心逻辑与模板分离,便于单元测试
- 可扩展性:未来可加入缩略图生成、视频搜索、标签等功能
- 可维护性:任何逻辑修改只需在
app/内处理,无需改动模板
templates/ —— HTML 模板
用途:
- 渲染网页结构,前端布局与后端逻辑分离
- 提供统一的头部、尾部和视频网格模板
核心文件:
header.php:包含 meta、CSS 引入、导航栏等footer.php:包含 JS 引入,闭合标签video_grid.php:循环渲染视频块、标题、分页和分类按钮
设计理念:
- 可复用:头尾公共部分只需修改一次
- 分层清晰:模板只负责渲染数据,不处理业务逻辑
- 可扩展:可轻松增加广告位、搜索框或筛选组件
config/ —— 可配置参数
用途:
- 集中管理项目可变参数,避免在代码中硬编码
核心文件:
config.php:- 上传目录路径
- 排除目录列表
- 每页显示的视频数量
- 支持的视频格式列表
设计理念:
- 集中配置:修改参数无需触碰核心逻辑或模板
- 灵活性:方便在不同服务器环境或不同需求下快速调整
核心功能实现
- Video.php:扫描视频、过滤格式、按时间排序、分页切片
- Category.php:自动生成分类按钮、排除无关目录
- index.php + video_grid.php:渲染分类按钮、分页与网格布局
- CSS & JS:保证统一比例、响应式布局、悬停自动播放
视频扫描与过滤(Video.php)
<?php
class Video {
private $uploadDir;
private $allowedFormats = ['mp4','webm','mov'];
public function __construct($uploadDir) {
$this->uploadDir = $uploadDir;
}
// 扫描目录及子目录获取视频列表
public function getVideos($category = null) {
$videos = [];
$dir = $category ? $this->uploadDir . '/' . $category : $this->uploadDir;
$videos = $this->scanDir($dir);
// 按修改时间倒序
usort($videos, function($a,$b){ return filemtime($b) - filemtime($a); });
return $videos;
}
private function scanDir($dir) {
$files = [];
if(!is_dir($dir)) return [];
foreach(scandir($dir) as $item){
if($item === '.' || $item === '..') continue;
$path = $dir . '/' . $item;
if(is_dir($path)){
$files = array_merge($files, $this->scanDir($path));
} else {
$ext = strtolower(pathinfo($item, PATHINFO_EXTENSION));
if(in_array($ext, $this->allowedFormats)){
$files[] = $path;
}
}
}
return $files;
}
}分类按钮生成(Category.php)
<?php
class Category {
private $uploadDir;
private $excludeDirs = ['Aspose.Words','ppress_uploads','sea'];
public function __construct($uploadDir){
$this->uploadDir = $uploadDir;
}
public function getCategories(){
$categories = [];
foreach(scandir($this->uploadDir) as $item){
if($item === '.' || $item === '..') continue;
$path = $this->uploadDir.'/'.$item;
if(is_dir($path) && !in_array($item, $this->excludeDirs)){
$categories[] = $item;
}
}
return $categories;
}
}分页逻辑(index.php)
<?php
require_once '../app/Video.php';
require_once '../app/Category.php';
require_once '../config/config.php';
$videoObj = new Video($uploadDir);
$categoryObj = new Category($uploadDir);
$category = $_GET['category'] ?? null;
$page = max(1, intval($_GET['page'] ?? 1));
$perPage = $perPage ?? 16;
$videos = $videoObj->getVideos($category);
$totalPages = ceil(count($videos)/$perPage);
$videosPage = array_slice($videos, ($page-1)*$perPage, $perPage);
$categories = $categoryObj->getCategories();
?>网格布局渲染(video_grid.php + style.css)
video_grid.php
<h2>视频展示</h2>
<!-- 分类按钮 -->
<div class="categories">
<?php foreach($categories as $cat): ?>
<a href="?category=<?=urlencode($cat)?>" class="category-btn"><?=$cat?></a>
<?php endforeach; ?>
</div>
<!-- 视频网格 -->
<div class="video-grid">
<?php foreach($videosPage as $video): ?>
<div class="video-item">
<video controls muted>
<source src="<?=str_replace('../public','',$video)?>" type="video/mp4">
</video>
</div>
<?php endforeach; ?>
</div>
<!-- 分页 -->
<div class="pagination">
<?php for($i=1;$i<=$totalPages;$i++): ?>
<a href="?category=<?=urlencode($category)?>&page=<?=$i?>" class="<?=($i==$page)?'active':''?>"><?=$i?></a>
<?php endfor; ?>
</div>style.css
.categories { margin-bottom: 15px; }
.category-btn { margin-right: 5px; padding:5px 10px; background:#eee; text-decoration:none; border-radius:4px; }
.category-btn:hover { background:#ccc; }
.video-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 10px;
}
.video-item video {
width: 100%;
aspect-ratio: 16/9;
object-fit: cover;
display: block;
}
.pagination a {
margin: 0 5px;
padding: 5px 10px;
background:#eee;
text-decoration:none;
border-radius:4px;
}
.pagination a.active { background:#999; color:#fff; }悬停自动播放(script.js)
document.addEventListener('DOMContentLoaded', function(){
document.querySelectorAll('.video-item video').forEach(function(video){
video.addEventListener('mouseenter', function(){ video.play(); });
video.addEventListener('mouseleave', function(){ video.pause(); });
});
});技术难点与解决方案
中文/空格文件名 URL 编码处理
难点:
- WordPress 上传目录中视频文件可能包含中文、空格或特殊字符
- 直接在 HTML
<video>标签中引用会导致浏览器无法识别路径,出现 404 错误或无法播放
解决方案:
- 在 PHP 后端处理视频路径时,使用
rawurlencode()对文件名进行编码 例如:
$videoUrl = str_replace('../public','', $video); $videoUrl = implode('/', array_map('rawurlencode', explode('/', $videoUrl)));- 保证生成的 URL 对浏览器友好,同时保留原有文件名信息
设计理念:
- 兼容性:无论中文、空格还是特殊字符都能正常播放
- 通用性:未来新增视频文件无需手动处理
竖屏视频比例统一显示
难点:
- 视频可能存在横屏(16:9)、竖屏(9:16)等不同比例
- 竖屏视频高度过高会破坏网格布局,影响整体视觉体验
解决方案:
- 使用 CSS
aspect-ratio或通过object-fit: cover强制统一显示比例 代码示例:
.video-item video { width: 100%; aspect-ratio: 16/9; object-fit: cover; }- 网格容器保持固定比例,视频内容自动裁剪,竖屏和横屏视频显示效果一致
设计理念:
- 视觉统一:保证网格整齐,不受视频原始尺寸影响
- 用户体验:每个视频块大小一致,页面浏览舒适
移动端适配与响应式布局
难点:
- 用户在手机、平板等不同屏幕上访问网站
- 网格列数、间距、按钮布局需要自适应
解决方案:
- 使用 CSS Grid 布局 +
auto-fill和minmax()控制列数 媒体查询调整移动端样式:
@media (max-width: 768px) { .video-grid { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); } }- 按钮、分页等元素也使用弹性布局或百分比宽度
设计理念:
- 响应式:无需单独开发移动端页面
- 体验一致:PC、平板、手机浏览视觉效果统一
分页和分类组合查询逻辑
难点:
- 用户可以选择分类并分页浏览视频
- 分类切换或分页时需要正确更新显示视频
- 需要避免数组越界或分页错误
解决方案:
后端逻辑统一:
- 根据分类过滤视频列表
- 按修改时间倒序排列
- 切片数组实现分页
$videosPage = array_slice($videos, ($page-1)*$perPage, $perPage);- 前端分页按钮根据总页数和当前页高亮显示
- 分类按钮点击时刷新页面或传递 GET 参数,保证分页逻辑正确
设计理念:
- 清晰逻辑:分页和分类相互独立又互相配合




Comments | NOTHING