huangfei vor 2 Jahren
Ursprung
Commit
8b76727709

+ 12 - 12
README.md

@@ -1,28 +1,28 @@
 # 监控视频流转换
 
 #### 一. 介绍
-用于将rtsp等协议的视频流转换为rtmp或hls协议的视频流,使其能在android、ios等设备上播放。  
+
+用于将rtsp等协议的视频流转换为rtmp或hls协议的视频流,使其能在android、ios等设备上播放。
 
 #### 二. 将视频流转为HLS并播放
 
-1.  调用 http://{server}:{port}/stream/url/encode 接口将直播地址编码处理为key
-2.  调用 http://{server}:{port}/stream/live/openHls/{key} 接口开始流转换
-3.  使用hls.js播放 http://{server}:{port}/stream/live/{key}/play.m3u8
-4.  定时访问 http://{server}:{port}/stream/live/ping/{key} 保持转换任务运行
+1. 调用 http://{server}:{port}/stream/url/encode 接口将直播地址编码处理为key
+2. 调用 http://{server}:{port}/stream/live/openHls/{key} 接口开始流转换
+3. 使用hls.js播放 http://{server}:{port}/stream/live/{key}/play.m3u8
+4. 定时访问 http://{server}:{port}/stream/live/ping/{key} 保持转换任务运行
 
-启动后访问:  http://{server}:{port}/stream/static/hls.html  可查看示例
+启动后访问:  http://{server}:{port}/stream/static/hls.html 可查看示例
 
 #### 三. 将视频流转为FLV并播放
 
-1.  调用 http://{server}:{port}/stream/url/encode 接口将直播地址编码处理为key
-2.  使用flv.js播放 http://{server}:{port}/stream/live/{key}.flv
-3.  定时访问 http://{server}:{port}/stream/live/ping/{key} 保持转换任务运行
+1. 调用 http://{server}:{port}/stream/url/encode 接口将直播地址编码处理为key
+2. 使用flv.js播放 http://{server}:{port}/stream/live/{key}.flv
+3. 定时访问 http://{server}:{port}/stream/live/ping/{key} 保持转换任务运行
 
 启动后访问:  
-http://{server}:{port}/stream/static/rtmp.html  可查看rtsp转rtmp示例
-
+http://{server}:{port}/stream/static/rtmp.html 可查看rtsp转rtmp示例
 
-http://{server}:{port}/stream/static/hls.html   可查看rtsp转hls示例
+http://{server}:{port}/stream/static/hls.html 可查看rtsp转hls示例
 
 
 

+ 26 - 26
src/main/java/com/coyee/stream/StreamApplication.java

@@ -21,31 +21,31 @@ import org.springframework.web.filter.CorsFilter;
 @SpringBootApplication(scanBasePackages = "com.coyee.stream")
 public class StreamApplication {
 
-	public static void main(String[] args) {
-		SpringApplication.run(StreamApplication.class, args);
-	}
+    public static void main(String[] args) {
+        SpringApplication.run(StreamApplication.class, args);
+    }
 
-	/**
-	 * 允许跨域访问
-	 * 
-	 * @return
-	 */
-	@Bean
-	public CorsFilter corsFilter() {
-		final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
-		final CorsConfiguration config = new CorsConfiguration();
-		config.setAllowCredentials(true); // 允许cookies跨域
-		config.addAllowedOrigin("*");// #允许向该服务器提交请求的URI,*表示全部允许,在SpringMVC中,如果设成*,会自动转成当前请求头中的Origin
-		config.addAllowedHeader("*");// #允许访问的头信息,*表示全部
-		config.setMaxAge(18000L);// 预检请求的缓存时间(秒),即在这个时间段里,对于相同的跨域请求不会再预检了
-		config.addAllowedMethod("OPTIONS");// 允许提交请求的方法,*表示全部允许
-		config.addAllowedMethod("HEAD");
-		config.addAllowedMethod("GET");// 允许Get的请求方法
-		config.addAllowedMethod("PUT");
-		config.addAllowedMethod("POST");
-		config.addAllowedMethod("DELETE");
-		config.addAllowedMethod("PATCH");
-		source.registerCorsConfiguration("/**", config);
-		return new CorsFilter(source);
-	}
+    /**
+     * 允许跨域访问
+     *
+     * @return
+     */
+    @Bean
+    public CorsFilter corsFilter() {
+        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
+        final CorsConfiguration config = new CorsConfiguration();
+        config.setAllowCredentials(true); // 允许cookies跨域
+        config.addAllowedOrigin("*");// #允许向该服务器提交请求的URI,*表示全部允许,在SpringMVC中,如果设成*,会自动转成当前请求头中的Origin
+        config.addAllowedHeader("*");// #允许访问的头信息,*表示全部
+        config.setMaxAge(18000L);// 预检请求的缓存时间(秒),即在这个时间段里,对于相同的跨域请求不会再预检了
+        config.addAllowedMethod("OPTIONS");// 允许提交请求的方法,*表示全部允许
+        config.addAllowedMethod("HEAD");
+        config.addAllowedMethod("GET");// 允许Get的请求方法
+        config.addAllowedMethod("PUT");
+        config.addAllowedMethod("POST");
+        config.addAllowedMethod("DELETE");
+        config.addAllowedMethod("PATCH");
+        source.registerCorsConfiguration("/**", config);
+        return new CorsFilter(source);
+    }
 }

+ 9 - 9
src/main/java/com/coyee/stream/config/SchedulerConfig.java

@@ -16,14 +16,14 @@ import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
 @Configuration
 @EnableScheduling
 public class SchedulerConfig {
-	@Bean
-	public TaskScheduler taskScheduler() {
-		ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
-		// 线程池大小
-		scheduler.setPoolSize(3);
-		// 线程名字前缀
-		scheduler.setThreadNamePrefix("task-thread-");
-		return scheduler;
-	}
+    @Bean
+    public TaskScheduler taskScheduler() {
+        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
+        // 线程池大小
+        scheduler.setPoolSize(3);
+        // 线程名字前缀
+        scheduler.setThreadNamePrefix("task-thread-");
+        return scheduler;
+    }
 
 }

+ 12 - 12
src/main/java/com/coyee/stream/config/StreamServerConfig.java

@@ -35,38 +35,38 @@ public class StreamServerConfig {
     /**
      * 单个分片播放时间
      */
-    private int hlsTime=5;
+    private int hlsTime = 5;
     /**
      * 最大分片数
      */
-    private int hlsWrap=20;
+    private int hlsWrap = 20;
     /**
      * 播放列表数
      */
-    private int hlsListSize=10;
+    private int hlsListSize = 10;
     /**
      * 没有观众时流的关闭时间,-1为不关闭
      */
-    private long expireMills=1000*60*5;
+    private long expireMills = 1000 * 60 * 5;
 
     /**
      * 系统启动时,先把hlsStoreDir里面的文件清除掉
      */
     @PostConstruct
     public void onStreamServerStart() throws IOException {
-        File dir=new File(hlsStoreDir);
-        if(dir.exists()==false){
+        File dir = new File(hlsStoreDir);
+        if (dir.exists() == false) {
             dir.mkdir();
         }
         log.info("准备清除hls目录的残留文件");
-        File[] children=dir.listFiles();
-        for(File child:children){
-            if(child.isDirectory()) {
+        File[] children = dir.listFiles();
+        for (File child : children) {
+            if (child.isDirectory()) {
                 FileUtils.deleteDirectory(child);
-                log.info("目录 已被清除:{}",child.getAbsolutePath());
-            }else{
+                log.info("目录 已被清除:{}", child.getAbsolutePath());
+            } else {
                 FileUtils.deleteQuietly(child);
-                log.info("文件 已被清除:{}",child.getAbsolutePath());
+                log.info("文件 已被清除:{}", child.getAbsolutePath());
             }
         }
     }

+ 5 - 5
src/main/java/com/coyee/stream/controller/StreamController.java

@@ -38,7 +38,7 @@ public class StreamController {
     /**
      * 开启hls流的转换,返回播放地址
      *
-     * @param key  加密后的流地址
+     * @param key      加密后的流地址
      * @param response
      * @param request
      */
@@ -155,9 +155,9 @@ public class StreamController {
             inputStream = new FileInputStream(tsFile);
             IOUtils.copy(inputStream, output);
             return null;
-        } catch(ClientAbortException er){
-            log.debug("客户端中断:"+er.getMessage());
-        }catch (Exception er) {
+        } catch (ClientAbortException er) {
+            log.debug("客户端中断:" + er.getMessage());
+        } catch (Exception er) {
             log.error("获取ts出错", er);
         } finally {
             if (inputStream != null) {
@@ -170,7 +170,7 @@ public class StreamController {
     /**
      * 打开一个flv流
      *
-     * @param key 加密后的流地址
+     * @param key      加密后的流地址
      * @param response
      * @param request
      */

+ 99 - 99
src/main/java/com/coyee/stream/controller/advice/GlobleExceptionHandler.java

@@ -23,111 +23,111 @@ import lombok.extern.slf4j.Slf4j;
 @RestControllerAdvice
 public class GlobleExceptionHandler {
 
-	/**
-	 * 404 异常捕捉处理
-	 * 
-	 * @param ex
-	 * @return
-	 */
-	@ExceptionHandler(value = org.springframework.web.servlet.NoHandlerFoundException.class)
-	public JsonResult errorHandler(org.springframework.web.servlet.NoHandlerFoundException ex) {
-		log.error(ex.getMessage(), ex);
-		return JsonResult.result(404, "服务接口不存在。");
-	}
+    /**
+     * 404 异常捕捉处理
+     *
+     * @param ex
+     * @return
+     */
+    @ExceptionHandler(value = org.springframework.web.servlet.NoHandlerFoundException.class)
+    public JsonResult errorHandler(org.springframework.web.servlet.NoHandlerFoundException ex) {
+        log.error(ex.getMessage(), ex);
+        return JsonResult.result(404, "服务接口不存在。");
+    }
 
-	/**
-	 * 参数校验出错
-	 * 
-	 * @param ex
-	 * @return
-	 */
-	@ExceptionHandler(value = BindException.class)
-	public JsonResult errorHandler(BindException ex) {
-		log.error(ex.getMessage(), ex);
-		return JsonResult.error().put("message", "参数不正确," + ex.getMessage());
-	}
+    /**
+     * 参数校验出错
+     *
+     * @param ex
+     * @return
+     */
+    @ExceptionHandler(value = BindException.class)
+    public JsonResult errorHandler(BindException ex) {
+        log.error(ex.getMessage(), ex);
+        return JsonResult.error().put("message", "参数不正确," + ex.getMessage());
+    }
 
-	/**
-	 * 参数校验出错
-	 * 
-	 * @param ex
-	 * @return
-	 */
-	@ExceptionHandler(value = BindValidationException.class)
-	public JsonResult errorHandler(BindValidationException ex) {
-		log.error(ex.getMessage(), ex);
-		return JsonResult.error().put("message", "参数不正确," + ex.getMessage());
-	}
+    /**
+     * 参数校验出错
+     *
+     * @param ex
+     * @return
+     */
+    @ExceptionHandler(value = BindValidationException.class)
+    public JsonResult errorHandler(BindValidationException ex) {
+        log.error(ex.getMessage(), ex);
+        return JsonResult.error().put("message", "参数不正确," + ex.getMessage());
+    }
 
-	/**
-	 * 请求的方式不支持
-	 * 
-	 * @param ex
-	 * @return
-	 */
-	@ExceptionHandler(value = HttpRequestMethodNotSupportedException.class)
-	public JsonResult errorHandler(HttpRequestMethodNotSupportedException ex) {
-		log.error(ex.getMessage(), ex);
-		return JsonResult.error().put("message", "Http 方式不正确," + ex.getMessage());
-	}
+    /**
+     * 请求的方式不支持
+     *
+     * @param ex
+     * @return
+     */
+    @ExceptionHandler(value = HttpRequestMethodNotSupportedException.class)
+    public JsonResult errorHandler(HttpRequestMethodNotSupportedException ex) {
+        log.error(ex.getMessage(), ex);
+        return JsonResult.error().put("message", "Http 方式不正确," + ex.getMessage());
+    }
 
-	/**
-	 * 非法的参数异常
-	 * 
-	 * @param ex
-	 * @return
-	 */
-	@ExceptionHandler(value = IllegalArgumentException.class)
-	public JsonResult errorHandler(IllegalArgumentException ex) {
-		log.error(ex.getMessage(), ex);
-		return JsonResult.error(500, "非法的参数," + ex.getMessage());
-	}
+    /**
+     * 非法的参数异常
+     *
+     * @param ex
+     * @return
+     */
+    @ExceptionHandler(value = IllegalArgumentException.class)
+    public JsonResult errorHandler(IllegalArgumentException ex) {
+        log.error(ex.getMessage(), ex);
+        return JsonResult.error(500, "非法的参数," + ex.getMessage());
+    }
 
-	/**
-	 * 类型异常
-	 * 
-	 * @param ex
-	 * @return
-	 */
-	@ExceptionHandler(value = HttpMediaTypeException.class)
-	public JsonResult errorHandler(HttpMediaTypeException ex) {
-		log.error(ex.getMessage(), ex);
-		return JsonResult.error().put("message", "服务异常,请检查参数及调用方式。详情内容为: " + ex.getMessage());
-	}
+    /**
+     * 类型异常
+     *
+     * @param ex
+     * @return
+     */
+    @ExceptionHandler(value = HttpMediaTypeException.class)
+    public JsonResult errorHandler(HttpMediaTypeException ex) {
+        log.error(ex.getMessage(), ex);
+        return JsonResult.error().put("message", "服务异常,请检查参数及调用方式。详情内容为: " + ex.getMessage());
+    }
 
-	/**
-	 * 拦截捕捉 CMSException.class
-	 * 
-	 * @param ex
-	 * @return
-	 */
-	@ExceptionHandler(value = HttpMessageConversionException.class)
-	public JsonResult errorHandler(HttpMessageConversionException ex) {
-		log.error(ex.getMessage(), ex);
-		return JsonResult.error().put("message", "参数不正确。详情内容为:" + ex.getMessage());
-	}
+    /**
+     * 拦截捕捉 CMSException.class
+     *
+     * @param ex
+     * @return
+     */
+    @ExceptionHandler(value = HttpMessageConversionException.class)
+    public JsonResult errorHandler(HttpMessageConversionException ex) {
+        log.error(ex.getMessage(), ex);
+        return JsonResult.error().put("message", "参数不正确。详情内容为:" + ex.getMessage());
+    }
 
-	/**
-	 * 全局异常捕捉处理
-	 * 
-	 * @param ex
-	 * @return
-	 */
-	@ExceptionHandler(value = Exception.class)
-	public JsonResult errorHandler(Exception ex) {
-		log.error(ex.getMessage(), ex);
-		return JsonResult.error().put("message", "服务异常,暂时不可用。" + ex.getMessage());
-	}
+    /**
+     * 全局异常捕捉处理
+     *
+     * @param ex
+     * @return
+     */
+    @ExceptionHandler(value = Exception.class)
+    public JsonResult errorHandler(Exception ex) {
+        log.error(ex.getMessage(), ex);
+        return JsonResult.error().put("message", "服务异常,暂时不可用。" + ex.getMessage());
+    }
 
-	/**
-	 * 参数不能解析
-	 * 
-	 * @param ex
-	 * @return
-	 */
-	@ExceptionHandler(value = HttpMessageNotReadableException.class)
-	public JsonResult errorHandler(HttpMessageNotReadableException ex) {
-		log.error(ex.getMessage(), ex);
-		return JsonResult.error().put("message", "参数解析异常,请检查参数格式。" + ex.getMessage());
-	}
+    /**
+     * 参数不能解析
+     *
+     * @param ex
+     * @return
+     */
+    @ExceptionHandler(value = HttpMessageNotReadableException.class)
+    public JsonResult errorHandler(HttpMessageNotReadableException ex) {
+        log.error(ex.getMessage(), ex);
+        return JsonResult.error().put("message", "参数解析异常,请检查参数格式。" + ex.getMessage());
+    }
 }

+ 10 - 10
src/main/java/com/coyee/stream/converter/Converter.java

@@ -13,17 +13,17 @@ import javax.servlet.AsyncContext;
  */
 public interface Converter {
 
-	/**
-	 * 添加一个流输出
-	 *
-	 * @param entity
-	 */
-	void addOutputStreamEntity(String key, AsyncContext entity) throws IOException;
+    /**
+     * 添加一个流输出
+     *
+     * @param entity
+     */
+    void addOutputStreamEntity(String key, AsyncContext entity) throws IOException;
 
-	/**
-	 * 要求关闭转换器
-	 */
-	void softClose();
+    /**
+     * 要求关闭转换器
+     */
+    void softClose();
 
 
 }

+ 2 - 2
src/main/java/com/coyee/stream/converter/FlvConverter.java

@@ -70,7 +70,7 @@ public class FlvConverter extends Thread implements Converter {
             if (avcodec.AV_CODEC_ID_H264 == grabber.getVideoCodec()
                     && (grabber.getAudioChannels() == 0 || avcodec.AV_CODEC_ID_AAC == grabber.getAudioCodec())) {
                 simpleTransFlv();
-            }else{
+            } else {
                 transFlv();
             }
         } catch (Exception e) {
@@ -250,6 +250,6 @@ public class FlvConverter extends Thread implements Converter {
 
     @Override
     public void softClose() {
-        this.running =false;
+        this.running = false;
     }
 }

+ 9 - 9
src/main/java/com/coyee/stream/converter/HlsConverter.java

@@ -57,7 +57,7 @@ public class HlsConverter extends Thread implements Converter {
     /**
      * 后续请求无须等待
      */
-    private boolean noWait=false;
+    private boolean noWait = false;
 
 
     public HlsConverter(StreamServerConfig config, String url, String key) {
@@ -104,13 +104,13 @@ public class HlsConverter extends Thread implements Converter {
             // 解码器格式
             recorder.setFormat("hls");
             // 单个切片时长,单位是s,默认为5s
-            int hlsTime=streamServerConfig.getHlsTime();
+            int hlsTime = streamServerConfig.getHlsTime();
             recorder.setOption("hls_time", String.valueOf(hlsTime));
             // HLS播放的列表长度,0标识不做限制
-            int hlsListSize=streamServerConfig.getHlsListSize();
+            int hlsListSize = streamServerConfig.getHlsListSize();
             recorder.setOption("hls_list_size", String.valueOf(hlsListSize));
             // TS文件数量限制
-            int hlsWrap=streamServerConfig.getHlsWrap();
+            int hlsWrap = streamServerConfig.getHlsWrap();
             recorder.setOption("hls_wrap", String.valueOf(hlsWrap));
             // 设置切片的ts文件序号起始值,默认从0开始,可以通过此项更改
             recorder.setOption("start_number", "100");
@@ -161,7 +161,7 @@ public class HlsConverter extends Thread implements Converter {
      */
     public File getM3u8File() {
         String hlsStoreDir = streamServerConfig.getHlsStoreDir();
-        String shortKey=this.getShortKey();
+        String shortKey = this.getShortKey();
         String hlsUrl = FilenameUtils.separatorsToSystem(hlsStoreDir + File.separator + shortKey + File.separator + "play.m3u8");
         File hlsFile = new File(hlsUrl);
         File hlsParentFile = hlsFile.getParentFile();
@@ -171,7 +171,7 @@ public class HlsConverter extends Thread implements Converter {
         return hlsFile;
     }
 
-    private String getShortKey(){
+    private String getShortKey() {
         return DigestUtils.md5Hex(this.key);
     }
 
@@ -195,13 +195,13 @@ public class HlsConverter extends Thread implements Converter {
 
 
     public String getPlayUrl() throws InterruptedException {
-        if(this.noWait==false) {//m3u8文件已存在就直接返回,不用等待新的ts文件解析完成
+        if (this.noWait == false) {//m3u8文件已存在就直接返回,不用等待新的ts文件解析完成
             lock.lock();
             condition.await(10, TimeUnit.SECONDS);
             lock.unlock();
         }
-        this.noWait=true;
-        String shortKey=this.getShortKey();
+        this.noWait = true;
+        String shortKey = this.getShortKey();
         return String.format("/live/%s/play.m3u8", shortKey);
     }
 

+ 132 - 131
src/main/java/com/coyee/stream/result/JsonResult.java

@@ -14,136 +14,137 @@ import com.alibaba.fastjson.JSONObject;
  */
 public class JsonResult extends HashMap<String, Object> implements Serializable {
 
-	private static final long serialVersionUID = 1L;
-	public static final int SUCCESS = 200;
-
-	public JsonResult() {
-	}
-
-	/**
-	 * 返回成功
-	 */
-	public static JsonResult ok() {
-		return ok("操作成功");
-	}
-
-	/**
-	 * 返回成功
-	 */
-	public static JsonResult okFallBack() {
-		return okFallBack("操作成功");
-	}
-
-	/**
-	 * 返回成功
-	 */
-	public JsonResult put(Object obj) {
-		return this.put("data", obj);
-	}
-
-	/**
-	 * 返回成功
-	 */
-	public static JsonResult ok(String message) {
-		return result(200, message);
-	}
-
-	/**
-	 * 降级函数 - 返回成功
-	 */
-	public static JsonResult okFallBack(String message) {
-		return result(205, message);
-	}
-
-	/**
-	 * 返回成功
-	 */
-	public static JsonResult result(int code, String message) {
-		JsonResult jsonResult = new JsonResult();
-		jsonResult.put("timestamp", System.currentTimeMillis());
-		jsonResult.put("status", code);
-		jsonResult.put("message", message);
-		return jsonResult;
-	}
-
-	/**
-	 * 设置数据
-	 * @param data
-	 * @return
-	 */
-	public JsonResult data(Object data){
-		this.put("data",data);
-		return this;
-	}
-
-	/**
-	 * 返回失败
-	 */
-	public static JsonResult error() {
-		return error("操作失败");
-	}
-
-	/**
-	 * 返回失败
-	 */
-	public static JsonResult error(String message) {
-		return error(500, message);
-	}
-
-	/**
-	 * 返回失败
-	 */
-	public static JsonResult error(int code, String message) {
-		JsonResult jsonResult = new JsonResult();
-		jsonResult.put("timestamp", System.currentTimeMillis());
-		jsonResult.put("status", code);
-		jsonResult.put("message", message);
-		return jsonResult;
-	}
-
-	/**
-	 * 设置code
-	 */
-	public JsonResult setCode(int code) {
-		super.put("status", code);
-		return this;
-	}
-
-	/**
-	 * 设置message
-	 */
-	public JsonResult setMessage(String message) {
-		super.put("message", message);
-		return this;
-	}
-
-	/**
-	 * 放入object
-	 */
-	@Override
-	public JsonResult put(String key, Object object) {
-		super.put(key, object);
-		return this;
-	}
-
-	/**
-	 * 权限禁止
-	 */
-	public static JsonResult forbidden(String message) {
-		JsonResult jsonResult = new JsonResult();
-		jsonResult.put("timestamp", System.currentTimeMillis());
-		jsonResult.put("status", 401);
-		jsonResult.put("message", message);
-		return jsonResult;
-	}
-
-	@Override
-	public String toString() {
-		return JSONObject.toJSONString(this);
-	}
-
-	public JSONObject toJSONObject() {
-		return JSONObject.parseObject(toString());
-	}
+    private static final long serialVersionUID = 1L;
+    public static final int SUCCESS = 200;
+
+    public JsonResult() {
+    }
+
+    /**
+     * 返回成功
+     */
+    public static JsonResult ok() {
+        return ok("操作成功");
+    }
+
+    /**
+     * 返回成功
+     */
+    public static JsonResult okFallBack() {
+        return okFallBack("操作成功");
+    }
+
+    /**
+     * 返回成功
+     */
+    public JsonResult put(Object obj) {
+        return this.put("data", obj);
+    }
+
+    /**
+     * 返回成功
+     */
+    public static JsonResult ok(String message) {
+        return result(200, message);
+    }
+
+    /**
+     * 降级函数 - 返回成功
+     */
+    public static JsonResult okFallBack(String message) {
+        return result(205, message);
+    }
+
+    /**
+     * 返回成功
+     */
+    public static JsonResult result(int code, String message) {
+        JsonResult jsonResult = new JsonResult();
+        jsonResult.put("timestamp", System.currentTimeMillis());
+        jsonResult.put("status", code);
+        jsonResult.put("message", message);
+        return jsonResult;
+    }
+
+    /**
+     * 设置数据
+     *
+     * @param data
+     * @return
+     */
+    public JsonResult data(Object data) {
+        this.put("data", data);
+        return this;
+    }
+
+    /**
+     * 返回失败
+     */
+    public static JsonResult error() {
+        return error("操作失败");
+    }
+
+    /**
+     * 返回失败
+     */
+    public static JsonResult error(String message) {
+        return error(500, message);
+    }
+
+    /**
+     * 返回失败
+     */
+    public static JsonResult error(int code, String message) {
+        JsonResult jsonResult = new JsonResult();
+        jsonResult.put("timestamp", System.currentTimeMillis());
+        jsonResult.put("status", code);
+        jsonResult.put("message", message);
+        return jsonResult;
+    }
+
+    /**
+     * 设置code
+     */
+    public JsonResult setCode(int code) {
+        super.put("status", code);
+        return this;
+    }
+
+    /**
+     * 设置message
+     */
+    public JsonResult setMessage(String message) {
+        super.put("message", message);
+        return this;
+    }
+
+    /**
+     * 放入object
+     */
+    @Override
+    public JsonResult put(String key, Object object) {
+        super.put(key, object);
+        return this;
+    }
+
+    /**
+     * 权限禁止
+     */
+    public static JsonResult forbidden(String message) {
+        JsonResult jsonResult = new JsonResult();
+        jsonResult.put("timestamp", System.currentTimeMillis());
+        jsonResult.put("status", 401);
+        jsonResult.put("message", message);
+        return jsonResult;
+    }
+
+    @Override
+    public String toString() {
+        return JSONObject.toJSONString(this);
+    }
+
+    public JSONObject toJSONObject() {
+        return JSONObject.parseObject(toString());
+    }
 
 }

+ 40 - 34
src/main/java/com/coyee/stream/service/IStreamService.java

@@ -5,6 +5,7 @@ import com.coyee.stream.converter.HlsConverter;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import java.util.Date;
+
 /**
  * @author hxfein
  * @className: IStreamService
@@ -14,39 +15,44 @@ import java.util.Date;
  */
 public interface IStreamService {
 
-	/**
-	 * 打开转换流
-	 * @param url
-	 * @param format
-	 * @param response
-	 * @param request
-	 * @return 播放地址
-	 */
-	String open(String url,String format, HttpServletResponse response, HttpServletRequest request);
-
-	/**
-	 * 更新流的上次访问时间
-	 * @param key
-	 */
-	boolean activeStream(String key);
-	/**
-	 * 流地址加密
-	 * @param url
-	 * @return
-	 */
-	String encode(String url);
-	
-	/**
-	 * 流地址解密
-	 * @param url
-	 * @return
-	 */
-	String decode(String url);
-
-	/**
-	 * 定时任务关闭长期未使用的转换器
-	 * 每5分钟执行一次
-	 */
-	void manageConverters();
+    /**
+     * 打开转换流
+     *
+     * @param url
+     * @param format
+     * @param response
+     * @param request
+     * @return 播放地址
+     */
+    String open(String url, String format, HttpServletResponse response, HttpServletRequest request);
+
+    /**
+     * 更新流的上次访问时间
+     *
+     * @param key
+     */
+    boolean activeStream(String key);
+
+    /**
+     * 流地址加密
+     *
+     * @param url
+     * @return
+     */
+    String encode(String url);
+
+    /**
+     * 流地址解密
+     *
+     * @param url
+     * @return
+     */
+    String decode(String url);
+
+    /**
+     * 定时任务关闭长期未使用的转换器
+     * 每5分钟执行一次
+     */
+    void manageConverters();
 
 }

+ 135 - 135
src/main/java/com/coyee/stream/service/impl/StreamServiceImpl.java

@@ -36,145 +36,145 @@ import lombok.extern.slf4j.Slf4j;
 @Service
 public class StreamServiceImpl implements IStreamService {
 
-	private Map<String, Converter> flvConverters = new HashMap<>();
-	private Map<String, Converter> hlsConverters = new HashMap<>();
-	private Map<String,Date> activeStreamMap=new HashMap<>();
-	@Resource
-	private StreamServerConfig streamServerConfig;
-	/**
-	 * 编码
-	 */
-	private static Charset charset = Charset.forName("utf-8");
+    private Map<String, Converter> flvConverters = new HashMap<>();
+    private Map<String, Converter> hlsConverters = new HashMap<>();
+    private Map<String, Date> activeStreamMap = new HashMap<>();
+    @Resource
+    private StreamServerConfig streamServerConfig;
+    /**
+     * 编码
+     */
+    private static Charset charset = Charset.forName("utf-8");
 
-	@Override
-	public String open(String url,String format, HttpServletResponse response, HttpServletRequest request) {
-		String key=encode(url);
-		this.activeStream(key);
-		//如果是hls协议,开启转换,转换成功以后返回播放地址给客户端
-		//如果是flv协议,开启转换,并保持与客户端的链接,不断的输出视频流给客户端
-		if(StringUtils.equals(format,"hls")){
-			HlsConverter hlsConverter=null;
-			if (hlsConverters.containsKey(key)==false) {
-				hlsConverter=new HlsConverter(streamServerConfig,url,key);
-				hlsConverter.start();
-				hlsConverters.put(key,hlsConverter);
-			}else{
-				hlsConverter=(HlsConverter)hlsConverters.get(key);
-				log.info("流{}的转换任务已存在,可以复用。", url);
-			}
-			try {
-				String playUrl = hlsConverter.getPlayUrl();
-				return playUrl;
-			}catch(InterruptedException er){
-				throw new RuntimeException("获取播放地址失败!");
-			}
-		}else {
-			AsyncContext async = request.startAsync();
-			async.setTimeout(0);
-			if (flvConverters.containsKey(key)) {
-				Converter c = flvConverters.get(key);
-				try {
-					c.addOutputStreamEntity(key, async);
-				} catch (IOException e) {
-					log.error(e.getMessage(), e);
-					throw new IllegalArgumentException(e.getMessage());
-				}
-			} else {
-				List<AsyncContext> outs = new ArrayList<>();
-				outs.add(async);
-				FlvConverter c = new FlvConverter(url, outs);
-				c.start();
-				flvConverters.put(key, c);
-			}
-			response.setContentType("video/x-flv");
-			response.setHeader("Connection", "keep-alive");
-			response.setStatus(HttpServletResponse.SC_OK);
-			try {
-				response.flushBuffer();
-			} catch (IOException e) {
-				log.error(e.getMessage(), e);
-			}
-		}
-		return null;
-	}
+    @Override
+    public String open(String url, String format, HttpServletResponse response, HttpServletRequest request) {
+        String key = encode(url);
+        this.activeStream(key);
+        //如果是hls协议,开启转换,转换成功以后返回播放地址给客户端
+        //如果是flv协议,开启转换,并保持与客户端的链接,不断的输出视频流给客户端
+        if (StringUtils.equals(format, "hls")) {
+            HlsConverter hlsConverter = null;
+            if (hlsConverters.containsKey(key) == false) {
+                hlsConverter = new HlsConverter(streamServerConfig, url, key);
+                hlsConverter.start();
+                hlsConverters.put(key, hlsConverter);
+            } else {
+                hlsConverter = (HlsConverter) hlsConverters.get(key);
+                log.info("流{}的转换任务已存在,可以复用。", url);
+            }
+            try {
+                String playUrl = hlsConverter.getPlayUrl();
+                return playUrl;
+            } catch (InterruptedException er) {
+                throw new RuntimeException("获取播放地址失败!");
+            }
+        } else {
+            AsyncContext async = request.startAsync();
+            async.setTimeout(0);
+            if (flvConverters.containsKey(key)) {
+                Converter c = flvConverters.get(key);
+                try {
+                    c.addOutputStreamEntity(key, async);
+                } catch (IOException e) {
+                    log.error(e.getMessage(), e);
+                    throw new IllegalArgumentException(e.getMessage());
+                }
+            } else {
+                List<AsyncContext> outs = new ArrayList<>();
+                outs.add(async);
+                FlvConverter c = new FlvConverter(url, outs);
+                c.start();
+                flvConverters.put(key, c);
+            }
+            response.setContentType("video/x-flv");
+            response.setHeader("Connection", "keep-alive");
+            response.setStatus(HttpServletResponse.SC_OK);
+            try {
+                response.flushBuffer();
+            } catch (IOException e) {
+                log.error(e.getMessage(), e);
+            }
+        }
+        return null;
+    }
 
-	@Override
-	public boolean activeStream(String key) {
-		if(hlsConverters.containsKey(key)||flvConverters.containsKey(key)) {
-			activeStreamMap.put(key, new Date());
-			return true;
-		}else{
-			activeStreamMap.remove(key);
-			return false;
-		}
-	}
+    @Override
+    public boolean activeStream(String key) {
+        if (hlsConverters.containsKey(key) || flvConverters.containsKey(key)) {
+            activeStreamMap.put(key, new Date());
+            return true;
+        } else {
+            activeStreamMap.remove(key);
+            return false;
+        }
+    }
 
-	@Override
-	public String encode(String url) {
-		String desKey=streamServerConfig.getDesKey();
-		return Des.encryptString(url, charset, desKey);
-	}
+    @Override
+    public String encode(String url) {
+        String desKey = streamServerConfig.getDesKey();
+        return Des.encryptString(url, charset, desKey);
+    }
 
-	@Override
-	public String decode(String url) {
-		String desKey=streamServerConfig.getDesKey();
-		return Des.decryptString(url, charset, desKey);
-	}
+    @Override
+    public String decode(String url) {
+        String desKey = streamServerConfig.getDesKey();
+        return Des.decryptString(url, charset, desKey);
+    }
 
-	@Scheduled(fixedDelay = 1*60*1000)
-	@Override
-	public void manageConverters() {
-		long expireMills=streamServerConfig.getExpireMills();
-		if(expireMills==-1){
-			log.info("因过期时间设为永久,管理任务没有运行:{}",new Date());
-			return;
-		}
-		log.info("管理任务开始运行:{}",new Date());
-		activeStreamMap.forEach((key,lastAccessTime)->{
-			if(lastAccessTime==null){
-				return ;
-			}
-			long accessTime=lastAccessTime.getTime();
-			long currentTime=System.currentTimeMillis();
-			long diff=currentTime-accessTime;
-			if(expireMills>=diff){
-				Converter flvConverter=flvConverters.get(key);
-				if(flvConverter!=null){
-					flvConverter.softClose();
-					flvConverters.remove(key);
-					log.info("管理任务移去FLV转流任务:{}",key);
-				}
-				Converter hlsConverter=hlsConverters.get(key);
-				if(hlsConverter!=null){
-					hlsConverter.softClose();
-					hlsConverters.remove(key);
-					log.info("管理任务移去HLS转流任务:{}",key);
-				}
-				activeStreamMap.remove(key);
-			}
-		});
-	}
+    @Scheduled(fixedDelay = 1 * 60 * 1000)
+    @Override
+    public void manageConverters() {
+        long expireMills = streamServerConfig.getExpireMills();
+        if (expireMills == -1) {
+            log.info("因过期时间设为永久,管理任务没有运行:{}", new Date());
+            return;
+        }
+        log.info("管理任务开始运行:{}", new Date());
+        activeStreamMap.forEach((key, lastAccessTime) -> {
+            if (lastAccessTime == null) {
+                return;
+            }
+            long accessTime = lastAccessTime.getTime();
+            long currentTime = System.currentTimeMillis();
+            long diff = currentTime - accessTime;
+            if (expireMills >= diff) {
+                Converter flvConverter = flvConverters.get(key);
+                if (flvConverter != null) {
+                    flvConverter.softClose();
+                    flvConverters.remove(key);
+                    log.info("管理任务移去FLV转流任务:{}", key);
+                }
+                Converter hlsConverter = hlsConverters.get(key);
+                if (hlsConverter != null) {
+                    hlsConverter.softClose();
+                    hlsConverters.remove(key);
+                    log.info("管理任务移去HLS转流任务:{}", key);
+                }
+                activeStreamMap.remove(key);
+            }
+        });
+    }
 
-	/**
-	 * 打开默认需要打开的流
-	 */
-	@PostConstruct
-	public void openDefaultStreams() throws IOException{
-		File defaultOpenListFile=new File("convertToHlsList.default");
-		if(defaultOpenListFile.exists()){
-			log.info("已找到默认hls配置文件:{},准备开启",defaultOpenListFile.getAbsolutePath());
-			List<String> hlsUrlList= FileUtils.readLines(defaultOpenListFile,"utf-8");
-			StreamServiceImpl that=this;
-			hlsUrlList.stream().forEach((url->{
-				Thread thread= new Thread(() -> {
-					log.info("准备开启{}的转流",url);
-					that.open(url,"hls",null,null);
-					log.info("{}的转流开启成功!!",url);
-				});
-				thread.start();
-			}));
-		}else {
-			log.info("未找到默认hls配置文件:{}",defaultOpenListFile.getAbsolutePath());
-		}
-	}
+    /**
+     * 打开默认需要打开的流
+     */
+    @PostConstruct
+    public void openDefaultStreams() throws IOException {
+        File defaultOpenListFile = new File("convertToHlsList.default");
+        if (defaultOpenListFile.exists()) {
+            log.info("已找到默认hls配置文件:{},准备开启", defaultOpenListFile.getAbsolutePath());
+            List<String> hlsUrlList = FileUtils.readLines(defaultOpenListFile, "utf-8");
+            StreamServiceImpl that = this;
+            hlsUrlList.stream().forEach((url -> {
+                Thread thread = new Thread(() -> {
+                    log.info("准备开启{}的转流", url);
+                    that.open(url, "hls", null, null);
+                    log.info("{}的转流开启成功!!", url);
+                });
+                thread.start();
+            }));
+        } else {
+            log.info("未找到默认hls配置文件:{}", defaultOpenListFile.getAbsolutePath());
+        }
+    }
 }

+ 102 - 102
src/main/java/com/coyee/stream/util/Des.java

@@ -17,116 +17,116 @@ import javax.crypto.spec.IvParameterSpec;
  */
 public class Des {
 
-	/**
-	 * 对给定的字符串以指定的编码方式和密钥进行加密
-	 * 
-	 * @param srcStr  待加密的字符串
-	 * @param charset 字符集,如utf8
-	 * @param sKey    密钥
-	 */
-	public static String encryptString(String srcStr, Charset charset, String sKey) {
-		byte[] src = srcStr.getBytes(charset);
-		byte[] buf = Des.encrypt(src, sKey);
-		return Des.parseByte2HexStr(buf);
-	}
+    /**
+     * 对给定的字符串以指定的编码方式和密钥进行加密
+     *
+     * @param srcStr  待加密的字符串
+     * @param charset 字符集,如utf8
+     * @param sKey    密钥
+     */
+    public static String encryptString(String srcStr, Charset charset, String sKey) {
+        byte[] src = srcStr.getBytes(charset);
+        byte[] buf = Des.encrypt(src, sKey);
+        return Des.parseByte2HexStr(buf);
+    }
 
-	/**
-	 * 对给定的密文以指定的编码方式和密钥进行解密
-	 * 
-	 * @param hexStr  需要解密的密文
-	 * @param charset 字符集
-	 * @param sKey    密钥
-	 * @return 解密后的原文
-	 */
-	public static String decryptString(String hexStr, Charset charset, String sKey) {
-		byte[] src = Des.parseHexStr2Byte(hexStr);
-		byte[] buf = Des.decrypt(src, sKey);
-		return new String(buf, charset);
-	}
+    /**
+     * 对给定的密文以指定的编码方式和密钥进行解密
+     *
+     * @param hexStr  需要解密的密文
+     * @param charset 字符集
+     * @param sKey    密钥
+     * @return 解密后的原文
+     */
+    public static String decryptString(String hexStr, Charset charset, String sKey) {
+        byte[] src = Des.parseHexStr2Byte(hexStr);
+        byte[] buf = Des.decrypt(src, sKey);
+        return new String(buf, charset);
+    }
 
-	public static byte[] encrypt(byte[] data, String sKey) {
-		try {
-			byte[] key = sKey.getBytes();
+    public static byte[] encrypt(byte[] data, String sKey) {
+        try {
+            byte[] key = sKey.getBytes();
 
-			IvParameterSpec iv = new IvParameterSpec(key);
-			DESKeySpec desKey = new DESKeySpec(key);
+            IvParameterSpec iv = new IvParameterSpec(key);
+            DESKeySpec desKey = new DESKeySpec(key);
 
-			SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
-			SecretKey securekey = keyFactory.generateSecret(desKey);
+            SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
+            SecretKey securekey = keyFactory.generateSecret(desKey);
 
-			Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
+            Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
 
-			cipher.init(Cipher.ENCRYPT_MODE, securekey, iv);
+            cipher.init(Cipher.ENCRYPT_MODE, securekey, iv);
 
-			return cipher.doFinal(data);
-		} catch (Throwable e) {
-			throw new RuntimeException(e);
-		}
-	}
+            return cipher.doFinal(data);
+        } catch (Throwable e) {
+            throw new RuntimeException(e);
+        }
+    }
 
-	/**
-	 * 解密
-	 * 
-	 * @param src
-	 * @param sKey
-	 * @return
-	 * @throws Exception
-	 */
-	public static byte[] decrypt(byte[] src, String sKey) {
-		try {
-			byte[] key = sKey.getBytes();
-			// 初始化向量
-			IvParameterSpec iv = new IvParameterSpec(key);
-			// 创建一个DESKeySpec对象
-			DESKeySpec desKey = new DESKeySpec(key);
-			// 创建一个密匙工厂
-			SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
-			// 将DESKeySpec对象转换成SecretKey对象
-			SecretKey securekey = keyFactory.generateSecret(desKey);
-			// Cipher对象实际完成解密操作
-			Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
-			// 用密匙初始化Cipher对象
-			cipher.init(Cipher.DECRYPT_MODE, securekey, iv);
-			// 真正开始解密操作
-			return cipher.doFinal(src);
-		} catch (Throwable e) {
-			throw new RuntimeException(e);
-		}
-	}
+    /**
+     * 解密
+     *
+     * @param src
+     * @param sKey
+     * @return
+     * @throws Exception
+     */
+    public static byte[] decrypt(byte[] src, String sKey) {
+        try {
+            byte[] key = sKey.getBytes();
+            // 初始化向量
+            IvParameterSpec iv = new IvParameterSpec(key);
+            // 创建一个DESKeySpec对象
+            DESKeySpec desKey = new DESKeySpec(key);
+            // 创建一个密匙工厂
+            SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
+            // 将DESKeySpec对象转换成SecretKey对象
+            SecretKey securekey = keyFactory.generateSecret(desKey);
+            // Cipher对象实际完成解密操作
+            Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
+            // 用密匙初始化Cipher对象
+            cipher.init(Cipher.DECRYPT_MODE, securekey, iv);
+            // 真正开始解密操作
+            return cipher.doFinal(src);
+        } catch (Throwable e) {
+            throw new RuntimeException(e);
+        }
+    }
 
-	/**
-	 * 将二进制转换成16进制
-	 *
-	 * @param buf
-	 * @return
-	 */
-	public static String parseByte2HexStr(byte buf[]) {
-		StringBuffer sb = new StringBuffer();
-		for (int i = 0; i < buf.length; i++) {
-			String hex = Integer.toHexString(buf[i] & 0xFF);
-			if (hex.length() == 1) {
-				hex = '0' + hex;
-			}
-			sb.append(hex.toUpperCase());
-		}
-		return sb.toString();
-	}
+    /**
+     * 将二进制转换成16进制
+     *
+     * @param buf
+     * @return
+     */
+    public static String parseByte2HexStr(byte buf[]) {
+        StringBuffer sb = new StringBuffer();
+        for (int i = 0; i < buf.length; i++) {
+            String hex = Integer.toHexString(buf[i] & 0xFF);
+            if (hex.length() == 1) {
+                hex = '0' + hex;
+            }
+            sb.append(hex.toUpperCase());
+        }
+        return sb.toString();
+    }
 
-	/**
-	 * 将16进制转换为二进制
-	 *
-	 * @param hexStr
-	 * @return
-	 */
-	public static byte[] parseHexStr2Byte(String hexStr) {
-		if (hexStr.length() < 1)
-			return null;
-		byte[] result = new byte[hexStr.length() / 2];
-		for (int i = 0; i < hexStr.length() / 2; i++) {
-			int high = Integer.parseInt(hexStr.substring(i * 2, i * 2 + 1), 16);
-			int low = Integer.parseInt(hexStr.substring(i * 2 + 1, i * 2 + 2), 16);
-			result[i] = (byte) (high * 16 + low);
-		}
-		return result;
-	}
+    /**
+     * 将16进制转换为二进制
+     *
+     * @param hexStr
+     * @return
+     */
+    public static byte[] parseHexStr2Byte(String hexStr) {
+        if (hexStr.length() < 1)
+            return null;
+        byte[] result = new byte[hexStr.length() / 2];
+        for (int i = 0; i < hexStr.length() / 2; i++) {
+            int high = Integer.parseInt(hexStr.substring(i * 2, i * 2 + 1), 16);
+            int low = Integer.parseInt(hexStr.substring(i * 2 + 1, i * 2 + 2), 16);
+            result[i] = (byte) (high * 16 + low);
+        }
+        return result;
+    }
 }

+ 93 - 89
src/main/resources/static/hls.html

@@ -1,109 +1,113 @@
 <!DOCTYPE html>
 <html lang="en" dir="ltr">
 <head>
-	<meta charset="utf-8">
-	<title>hls测试</title>
-	<script src="https://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
-	<script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
+    <meta charset="utf-8">
+    <title>hls测试</title>
+    <script src="https://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
+    <script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
 </head>
 <body>
 <div>
-	<video id="video" controls></video>
+    <video id="video" controls></video>
 </div>
 <div>
-	<h3>1.地址编码</h3>
-	<p>
-		<label>源地址:</label>
-		<input type="text" size="120" value="rtsp://wowzaec2demo.streamlock.net/vod/mp4" id="sourceUrl"/>
-		<input type="button" onclick="createKey()" value="编码"/>
-	</p>
-	<p><label>编码:</label><label id="keyText"></label></p>
-	<p><label>打开流地址:</label><label id="openHlsUrlText"></label></p>
+    <h3>1.地址编码</h3>
+    <p>
+        <label>源地址:</label>
+        <input type="text" size="120" value="rtsp://wowzaec2demo.streamlock.net/vod/mp4" id="sourceUrl"/>
+        <input type="button" onclick="createKey()" value="编码"/>
+    </p>
+    <p><label>编码:</label><label id="keyText"></label></p>
+    <p><label>打开流地址:</label><label id="openHlsUrlText"></label></p>
 </div>
 <div>
-	<h3>2.打开流转换</h3>
-	<p>
-		<input type="text" size="120" value="" id="openHlsUrl"/>
-		<input type="button" onclick="openHlsConvert()" value="打开流转换"/>
-	</p>
-	<p><label>播放地址:</label><label id="playUrlText"></label></p>
+    <h3>2.打开流转换</h3>
+    <p>
+        <input type="text" size="120" value="" id="openHlsUrl"/>
+        <input type="button" onclick="openHlsConvert()" value="打开流转换"/>
+    </p>
+    <p><label>播放地址:</label><label id="playUrlText"></label></p>
 </div>
 <div>
-	<h3>3.播放流</h3>
-	<input type="text" size="120" value="" id="hlsUrl"/>
-	<input type="button" onclick="playHls()" value="播放"/>
+    <h3>3.播放流</h3>
+    <input type="text" size="120" value="" id="hlsUrl"/>
+    <input type="button" onclick="playHls()" value="播放"/>
 </div>
 
 <div>
-	<h3>4.保持会话</h3>
-	<p>
-		<input type="text" size="120" value="" id="key"/>
-		<input type="button" onclick="keepActive()" value="保持"/>
-	</p>
-	<p id="keepMsg"></p>
+    <h3>4.保持会话</h3>
+    <p>
+        <input type="text" size="120" value="" id="key"/>
+        <input type="button" onclick="keepActive()" value="保持"/>
+    </p>
+    <p id="keepMsg"></p>
 </div>
 <script type="text/javascript">
-	var baseUrl=window.location.href.split("/static/")[0];
-	function createKey(){
-		var sourceUrl=$("#sourceUrl").val()||'';
-		if(sourceUrl==''){
-			alert("源地址不能为空!");
-			return;
-		}
-		var url=baseUrl+"/url/encode";
-		$.post(url,{url:sourceUrl},function(json){
-			if(json.status==200){
-				var key=json.data;
-				$("#keyText").text(key);
-				$("#key").val(key);
-				var openHlsUrl=baseUrl+"/live/openHls/"+key;
-				$("#openHlsUrlText").text(openHlsUrl);
-				$("#openHlsUrl").val(openHlsUrl);
-			}else{
-				alert("错误:"+json.message);
-			}
-		});
-	}
-	function openHlsConvert(){
-		var openHlsUrl=$("#openHlsUrl").val()||'';
-		if(openHlsUrl==''){
-			alert("接口地址不能为空!");
-			return;
-		}
-		$.get(openHlsUrl,function(json){
-			if(json.status==200){
-				var playUrl=baseUrl+json.data;
-				$("#playUrlText").text(playUrl);
-				$("#hlsUrl").val(playUrl);
-			}else{
-				alert("打开流转换失败:"+json.message);
-			}
-		});
-	}
-	function playHls(){
-		var url=$("#hlsUrl").val()||'';
-		if(url==''){
-			alert("请输入hls流播放地址!");
-			return;
-		}
-		var video = document.getElementById('video');
-		var hls = new Hls();
-		hls.loadSource(url);
-		hls.attachMedia(video);
-		hls.on(Hls.Events.MANIFEST_PARSED, function() {
-			video.play();
-		});
-	}
-	function keepActive(){
-		var key=$("#key").val()||"";
-		var index=0;
-		setInterval(function(){
-			var url=baseUrl+"/live/ping/"+key;
-			$.get(url,function(text){
-				$("#keepMsg").text("第"+(index++)+"次保持会话:"+text);
-			});
-		},5*1000);
-	}
+    var baseUrl = window.location.href.split("/static/")[0];
+
+    function createKey() {
+        var sourceUrl = $("#sourceUrl").val() || '';
+        if (sourceUrl == '') {
+            alert("源地址不能为空!");
+            return;
+        }
+        var url = baseUrl + "/url/encode";
+        $.post(url, {url: sourceUrl}, function (json) {
+            if (json.status == 200) {
+                var key = json.data;
+                $("#keyText").text(key);
+                $("#key").val(key);
+                var openHlsUrl = baseUrl + "/live/openHls/" + key;
+                $("#openHlsUrlText").text(openHlsUrl);
+                $("#openHlsUrl").val(openHlsUrl);
+            } else {
+                alert("错误:" + json.message);
+            }
+        });
+    }
+
+    function openHlsConvert() {
+        var openHlsUrl = $("#openHlsUrl").val() || '';
+        if (openHlsUrl == '') {
+            alert("接口地址不能为空!");
+            return;
+        }
+        $.get(openHlsUrl, function (json) {
+            if (json.status == 200) {
+                var playUrl = baseUrl + json.data;
+                $("#playUrlText").text(playUrl);
+                $("#hlsUrl").val(playUrl);
+            } else {
+                alert("打开流转换失败:" + json.message);
+            }
+        });
+    }
+
+    function playHls() {
+        var url = $("#hlsUrl").val() || '';
+        if (url == '') {
+            alert("请输入hls流播放地址!");
+            return;
+        }
+        var video = document.getElementById('video');
+        var hls = new Hls();
+        hls.loadSource(url);
+        hls.attachMedia(video);
+        hls.on(Hls.Events.MANIFEST_PARSED, function () {
+            video.play();
+        });
+    }
+
+    function keepActive() {
+        var key = $("#key").val() || "";
+        var index = 0;
+        setInterval(function () {
+            var url = baseUrl + "/live/ping/" + key;
+            $.get(url, function (text) {
+                $("#keepMsg").text("第" + (index++) + "次保持会话:" + text);
+            });
+        }, 5 * 1000);
+    }
 </script>
 </body>
 </html>

+ 2 - 3
src/main/resources/static/rtmp.html

@@ -47,7 +47,7 @@
             alert("源地址不能为空!");
             return;
         }
-        var url = baseUrl+"/url/encode";
+        var url = baseUrl + "/url/encode";
         $.post(url, {url: sourceUrl}, function (json) {
             if (json.status == 200) {
                 var key = json.data;
@@ -63,7 +63,6 @@
     }
 
 
-
     function playRtmp() {
         var url = $("#rtmpUrl").val() || '';
         if (url == '') {
@@ -74,7 +73,7 @@
             var videoElement = document.getElementById('rtmpPlayer');
             var flvPlayer = flvjs.createPlayer({
                 type: 'flv',
-                url:url
+                url: url
             });
             flvPlayer.attachMediaElement(videoElement);
             flvPlayer.load();

+ 8 - 12
src/test/java/com/coyee/stream/AppTest.java

@@ -7,32 +7,28 @@ import junit.framework.TestSuite;
 /**
  * Unit test for simple App.
  */
-public class AppTest 
-    extends TestCase
-{
+public class AppTest
+        extends TestCase {
     /**
      * Create the test case
      *
      * @param testName name of the test case
      */
-    public AppTest( String testName )
-    {
-        super( testName );
+    public AppTest(String testName) {
+        super(testName);
     }
 
     /**
      * @return the suite of tests being tested
      */
-    public static Test suite()
-    {
-        return new TestSuite( AppTest.class );
+    public static Test suite() {
+        return new TestSuite(AppTest.class);
     }
 
     /**
      * Rigourous Test :-)
      */
-    public void testApp()
-    {
-        assertTrue( true );
+    public void testApp() {
+        assertTrue(true);
     }
 }