一款极简的流媒体Web服务器(Streaming Media Web Server),提供视频音乐的在线播放功能

A extremely simple web server of streaming media, which can provide online play of video and audio

项目源码:https://github.com/HackyleShawe/StreamingMediaWebServer

  • 在硬盘上的高清电影,难道就只能在电脑上才能看吗?我想在手机上看行不行呢?
  • 类似NAS(Network Attached Storage,网络附属存储)一样,搭建一个局域网私有视频云服务器
  • 本项目的核心目的是做一个类似于 视频NAS ,可以 在局域网下提供视频音乐、在线观看服务
  • 市场上已有的局域网流媒体播放器平台

  • flex:收费
  • jellyfin:免费;坑多,可用性较差
  • emby:收费;可用性还行
  • Features

  • 以文件夹为单位, 递归收集 指定目录下的 所有文件
  • 虽然可收集所有类型的文件,但目前只可以 在线观看视频、音乐 ,其他类型文件赞不支在线观看
  • 视频 文件的两种预览模式
  • 列表模式:依次展示每个文件夹下的所有文件
  • 宫格模式:以384px * 216px(是1920*1080的同比缩放)的格式展示视频、音乐文件
  • 文件管理: 文件删除、文件重命名
  • Table of Contents

  • 环境检查与配置
  • 设置系统编码
  • Java环境
  • 下载Tomcat
  • 配置Tomcat
  • index.jsp
  • delete.jsp
  • rename.jsp
  • 为什么要检查系统编码:使得支持中文,可以正常显示中文的视频文件,以及解析 URL 中的中文

    修改为 UTF-8

    进入在root目录,在.bashrc和.profile文件中追加以下数据:

    export LC_ALL=en_US.UTF-8
    export LANG=en_US.UTF-8
    export LANGUAGE=en_US.UTF-8
    source .bashrc
    source .profile

    Java 环境

    这里以 aarch64 的树莓派 为例,演示安装过程。其他Linux和Windows平台同理。

    查看系统平台: uname -a

    根据平台下载JDK: https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html

    上传解压:

  • 在opt目录下创建Java文件夹,专门存放jdk数据:/opt/java
  • 将jdk数据包上传并解压:tar -zxvf jdk-8u301-linux-aarch64.tar.gz
  • 注意:usr存放的是系统自带的程序数据包,opt存放的是用户的数据包。

    添加环境变量:

  • 编辑:vim /etc/profile
  • 追加以下数据:
  • export JAVA_HOME=/opt/jdk1.8.0_301
    export PATH=$JAVA_HOME/bin/:$PATH
    export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
  • 使得配置的环境变量立即生效:source /etc/profile
  • 查看是否配置成功:java -version
  • 下载Tomcat

    下载: https://tomcat.apache.org/download-90.cgi

    上传解压:

  • 在opt目录下创建Tomcat文件夹,专门存放jdk数据:/opt/tomcat
  • 将jdk数据包上传并解压:tar -zxvf apache-tomcat-9.0.54.tar.gz
  • 解压后将文件夹名字改短一点,方便以后使用:mv apache-tomcat-9.0.54 tomcat9
  • 修改配置文件 conf/server.xml ,自定义端口和 URI 编码为 UTF-8

    配置 Tomcat

    在Tomcat安装目录下的conf/server.xml中指定静态资源的目录,以及解析地址

    凡是以/media开头的请求,全部都会解析到docBase指定的目录下

    docBase 指定的目录是视频文件的存放地点

  • 下载源码包:https://github.com/HackyleShawe/StreamingMediaWebServer
  • 将其解压,重命名为一个简单的名字,我重命名为:ms,意为Media Server
  • 复制ms到到%Tomcat%/webapps/
  • 编辑WEB-INF/config.properties文件,指定为在配置Tomcat事conf/server.xml中指定静态资源的目录,也即docBase的值
  • 启动Tomcat,指定%Tomcat%/bin/shartup.bat
  • 访问项目:http://localhost:8080/ms/
  • 创建一个专门存放视频文件的目录,例如:D:\A03-Program\A-Programming\Media
  • 配置Tomcat将所有的静态资源请求全部解析到该个文件夹下
  • 写一个JSP来读取该目录下的所有媒体文件
  • 点击媒体文件后,经过Tomcat就解析到了媒体文件的真实路径
  • index.jsp

  • 读取配置文件中指定路径下的所有视频文件,以文件夹为单位暂存
  • 两种呈现方式:列表、宫格
  • 读取配置文件主要逻辑

    String projectPath = request.getSession().getServletContext().getRealPath(""); //获取项目的根目录
    String path = projectPath + "/WEB-INF/config.properties";
    //从配置文件中读取媒体文件的所在目录
    FileInputStream is = new FileInputStream(path);
    Properties pro = new Properties();
    pro.load(is);
    String mediaPath = pro.getProperty("media-path");

    以文件夹为单位,收集该路径下的所有文件,暂存与Map

    //递归收集路径(mediaPath)下的所有文件存于fileMap中
    public void collectFile(String mediaPath, File file, Map<String, List<String>> fileMap) {
      if(!file.exists()) {
        return;
      //收集文件
      File[] files = file.listFiles(File::isFile);
      if(files != null && files.length > 0) {
        String name = file.getAbsolutePath().replace(File.separator, "/");
        List<String> collect = Arrays.stream(files).map(ff -> ff.getName()).collect(Collectors.toList());
        fileMap.put(name.replace(mediaPath, ""), collect);
      //收集文件夹
      File[] dirs = file.listFiles(File::isDirectory);
      if(dirs == null || dirs.length < 1) {
        return;
      //递归收集文件夹下的子文件、文件夹
      for (File dir : dirs) {
        collectFile(mediaPath, dir, fileMap);
    

    两种呈现模式,基于JSTL的choose-when标签,类似于Java的switch-case

    <c:choose>
      <c:when test="${!empty mode && mode == 'list' }"> <%-- else if 的意思 --%>
        <p>No.${varSta.count}
          <a href="delete.jsp?dir=${entry.key}&name=${name}"><b>删除</b></a>
          <a href="rename.jsp?dir=${entry.key}&name=${name}"><b>重命名</b></a>
          <a target="_blank" href="/media/${entry.key}/${name}">${name}</a>
      </c:when>
      <c:otherwise> <%-- else的意思 --%>
        <!-- 视频的高度、宽度是按照1920*1080同比例缩小 -->
        <video src="/media/${entry.key}/${name}" width="384px" height="216px" controls preload="auto"></video>
      </c:otherwise>
    </c:choose>

    delete.jsp

    删除文件:调用java.util.File的delete方法

    <!-- 删除确认 -->
    <c:if test="${!empty deleted && deleted == 'yes' }">
        String rootname = mediaRoot+"/"+dir+"/"+name;
        // out.println(rootname);
        File file = new File(rootname);
        boolean del = file.delete();
        // out.println(del);
        response.sendRedirect("index.jsp?mode=list");
    </c:if>

    rename.jsp

    重命名文件:调用java.util.File的renameTo方法

    <!-- 确认重命名 -->
    <c:if test="${!empty renamed && renamed == 'yes' }">
        String oldDir = request.getParameter("oldDir");
        String newDir = request.getParameter("newDir");
        String oldName = request.getParameter("oldName");
        String newName = request.getParameter("newName");
        String oldname = mediaRoot + oldDir +"/"+ oldName;
        String newname = mediaRoot + newDir +"/"+ newName;
        if(!oldname.equals(newname)) {
          File oldFile = new File(oldname);
          File newFile = new File(newname);
          if(oldFile.exists()) {
            oldFile.renameTo(newFile);
            response.sendRedirect("index.jsp?mode=list");
          } else {
            out.println("路径不存在: " + oldname);
            out.println("<h1><a href='index.jsp?mode=list'>返回</a></h1>");
    </c:if>