最近项目中有播放视频的需求,技术选型采用UMS播放器,免费版只能播放FLV格式的视频文件,因此需要对用户上传的视频进行格式转换,转换工具为FormatFactory,功能还是比较强大的。但是面临的一个问题,视频转换是非常耗时的,上传完直接转换是没法接受的,于是决定采用quartz,以任务调度的方式,在后台进行转换,具体步骤如下:
1.定义一个任务队列,将待转换的视频文件信息放到队列中。采用单例模式,并且考虑到线程安全问题,采用线程安全的Vector作为队列容器:
/** * 格式转换任务队列 * 队列中放的是ResourceInfo类型对象 * @author Administrator * */ public class TransformTaskQueue { private static TransformTaskQueue instance = null; //实际存放转换对象信息的队列,采用线程安全的Vercor容器 public static Vector<ResourceInfo> taskQueue = new Vector<ResourceInfo>(); public static TransformTaskQueue getInstance() { if (instance == null) { instance = new TransformTaskQueue(); } return instance; } /** * 向队列中添加对象 * @param info */ public static void add(ResourceInfo info) { taskQueue.add(info); } /** * 从队列中删除对象 * @param info */ public static void remove(ResourceInfo info) { if(taskQueue.size()>0 && taskQueue.contains(info)){ taskQueue.remove(info); } } }
2.用户上传视频文件之后,后台进行判断,如果不是flv格式,则将文件转换所需信息封装到ResuorceInfo对象,将该对象放入待转换队列:
// 如果源视频文件存在,则进行相应的转换,转换为FLV文件 if (new File(TransConfig.VIDEO_SOURCE_ROOT + path + fileName).exists()) { ResourceInfo info = new ResourceInfo(); info.setResourceId(resourceId); info.setPath(path); info.setFileName(fileName); info.setStatus(0); // 添加到转换队列 TransformTaskQueue.add(info); } else { System.out.println("源文件不存在!"); }
3.执行单个具体文件转换的操作类代码如下:
/** * 执行具体转换操作的类, * 采用多线程技术,继承了runnable接口 * @author Administrator * */ public class TransformExecutor implements Runnable,Serializable{ private static final long serialVersionUID = 1L; private ResourceInfo info = null ; public TransformExecutor(ResourceInfo info){ this.info = info; } @Override public void run() { String resourceId = info.getResourceId(); String path = info.getPath(); String fileName = info.getFileName(); String videoFilename = TransConfig.VIDEO_SOURCE_ROOT + path + fileName; String flvFilename = path + FileUtil.getFilePrefix(fileName) + ".flv"; // 转换成功,修改数据库中的is_transed字段为1 if (Video2FLVTransfer.transform(videoFilename, flvFilename) == 1) { CRUDUtil.update(resourceId, 1); } // 转换失败,修改数据库中的is_transed字段为2 else { CRUDUtil.update(resourceId, 2); } // 将resourceInfo从转换队列中去除 TransformTaskQueue.remove(info); } }
4.下面是开启多线程转换的操作类,采用线程池技术,因为转换视频文件格式工作量比较大,因此规定每次最多开启3个线程:
/** * 转换执行器服务类 * @author Administrator * */ public class TransExecutorService { private final ExecutorService pool; private static TransExecutorService instance; //线程池大小,即每次最多允许开启几个线程执行转换操作 private static final int THREAD_SIZE = 3; public static TransExecutorService getInstance() { if (instance == null) { instance = new TransExecutorService(); } return instance; } private TransExecutorService() { // pool = Executors.newCachedThreadPool(); pool = Executors.newFixedThreadPool(THREAD_SIZE); } /** * 开启新线程,执行转换操作 * @param info */ public void execute(ResourceInfo info) { try { pool.submit(new TransformExecutor(info)); } catch (Exception e) { e.printStackTrace(); } } public synchronized void shutdown() { pool.shutdown(); } }
5.调度任务实现类,即每次执行调度,执行的操作
/** * 调度任务具体执行类 * @author Administrator * */ public class TransformJob implements Job { @Override public void execute(JobExecutionContext ctx) throws JobExecutionException { //获取当前待转换视频文件队列 Vector<ResourceInfo> infos = TransformTaskQueue.getInstance().taskQueue; System.out.println("size:"+infos.size()); //如果任务队列中存在待转换对象,则进行转换 if (infos.size() > 0) { for (int i=0;i<infos.size();i++) { //status为0,表示不是正在转换中的 if (infos.get(i).getStatus() == 0) { infos.get(i).setStatus(1); //新开线程,执行转换操作 TransExecutorService.getInstance().execute(infos.get(i)); } } } } }
6.任务调度管理类,规定了调度执行的一些规则,其中定时表达式请自行网上搜索,这里采用的是每10秒执行一次。
/** * 格式转换任务调度管理类 * * @author Administrator * */ public class SchedulManager { private static SchedulManager instance = new SchedulManager(); private Scheduler scheduler; private volatile boolean start = false; private SchedulManager() { try { SchedulerFactory factory = new StdSchedulerFactory(); scheduler = factory.getScheduler(); } catch (SchedulerException e) { e.printStackTrace(); } } public static SchedulManager getInstance() { return instance; } /** * 开始执行,将加载调度配置并启动每个调度。 * * @注意: 一般在程序启动时调用该方法。 */ public void execute() { try { // 加载调度配置,并启动每个调度。 scheduleJobs(); scheduler.start(); } catch (Exception e) { e.printStackTrace(); } } /** * 加载调度配置并启动每个调度 * * @注意: TODO */ @SuppressWarnings("static-access") private void scheduleJobs() { Vector<ResourceInfo> infos = TransformTaskQueue.getInstance().taskQueue; System.out.println("size:" + infos.size()); if (infos.size() > 0) { start(); } start = true; } /** * 根据ResourceInfo启动一个调度 * * @注意: 内部方法,外部不能调用 * @param ResourceInfo * 资源信息 */ private void start() { try { // String id = info.getResourceId(); // 构造方法中 第一个是任务名称 ,第二个是任务组名,第三个是任务执行的类 JobDetail jobDetail = new JobDetail("video_trans_id", Scheduler.DEFAULT_GROUP, TransformJob.class); String cronExpr = "0/10 * * * * ?"; String triggerName = "video_trans_trigger"; Trigger trigger = new CronTrigger(triggerName, Scheduler.DEFAULT_GROUP, cronExpr); scheduler.scheduleJob(jobDetail, trigger); } catch (Exception e) { System.out.println("出错"); e.printStackTrace(); } } public void init() { SchedulManager sm = SchedulManager.getInstance(); // sm.start(info); // sm.scheduleJobs(); sm.start(); try { sm.scheduler.start(); } catch (SchedulerException e) { // TODO 自动生成的 catch 块 e.printStackTrace(); } }
7.通过上述6个步骤,已经可以通过quartz以任务调度的形式来进行格式转换了,接下来的问题,是写一个listener类,以实现在服务器启动的时候,任务调度自动启动。
首先需要在web.xml中加入如下配置:
<listener> <listener-class>com.yunda.web.EventTransformStartupListener</listener-class> </listener>
之后就是实现配置文件中的实现监听功能的类,非常简单,就是调用SchedulManager中的init()方法即可,代码如下:
public class EventTransformStartupListener implements ServletContextListener { @Override public void contextDestroyed(ServletContextEvent arg0) { } @Override public void contextInitialized(ServletContextEvent arg0) { System.out.println("init..."); SchedulManager sm = SchedulManager.getInstance(); //sm.start(info); sm.init(); } }
至此,后台进行格式转换的功能全部完成,通过做这个功能,发现quartz采用的任务调度机制,跟linux的crontab差不多,也是采用定时扫描的方法来完成,连定时表达式的规则都长的差不多。先写这么多吧,就是学习了quartz和多线程的简单用法,留个笔记,以便日后深究^_^