最近項目中有播放視頻的需求,技術選型采用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和多線程的簡單用法,留個筆記,以便日后深究^_^