package org.oneliveweb.live.streams; import java.io.File; import java.nio.file.Files; import java.nio.file.attribute.PosixFilePermission; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.entermediadb.asset.Asset; import org.entermediadb.asset.MediaArchive; import org.entermediadb.video.CloudTranscodeManager; import org.ijsolutions.testbench.TestBench; import org.openedit.CatalogEnabled; import org.openedit.Data; import org.openedit.ModuleManager; import org.openedit.OpenEditException; import org.openedit.data.Searcher; import org.openedit.page.Page; import org.openedit.page.manage.PageManager; import org.openedit.repository.ContentItem; import org.openedit.repository.filesystem.StringItem; import org.openedit.users.User; import org.openedit.util.Exec; import org.openedit.util.ExecResult; import org.openedit.util.PathProcessor; public class LiveStreamManager implements CatalogEnabled { protected ModuleManager fieldModuleManager; protected Exec fieldExec; LongRunningProcess fieldNginxProcess; protected String fieldCatalogId; private static final Log log = LogFactory.getLog(LiveStreamManager.class); protected CloudTranscodeManager fieldTranscodeManager; protected HashMap fieldStreams; protected boolean fieldLocal = false; public boolean isLocal() { return fieldLocal; } public void setLocal(boolean inLocal) { fieldLocal = inLocal; } public CloudTranscodeManager getTranscodeManager() { return fieldTranscodeManager; } public void setTranscodeManager(CloudTranscodeManager inTranscodeManager) { fieldTranscodeManager = inTranscodeManager; } public HashMap getStreams() { if (fieldStreams == null) { fieldStreams = new HashMap(); } return fieldStreams; } public void setStreams(HashMap inStreams) { fieldStreams = inStreams; } public boolean isRunning() { return getNginxProcess().isRunning(); } public String getCatalogId() { return fieldCatalogId; } public void setCatalogId(String inCatalogId) { fieldCatalogId = inCatalogId; } public void setNginxProcess(LongRunningProcess inNginxProcess) { fieldNginxProcess = inNginxProcess; } public MediaArchive getMediaArchive() { return (MediaArchive) getModuleManager().getBean(getCatalogId(), "mediaArchive"); } public TestBench getTestBench() { return (TestBench) getModuleManager().getBean(getCatalogId(), "testBench"); } public ModuleManager getModuleManager() { return fieldModuleManager; } public void setModuleManager(ModuleManager inModuleManager) { fieldModuleManager = inModuleManager; } public Exec getExec() { return fieldExec; } public void setExec(Exec fieldExec) { this.fieldExec = fieldExec; } public void startNginx() { // if (fieldNginxProcess != null) { // getNginxProcess().forceDestroyProcess();// just for testing, delete this // fieldNginxProcess = null; // } if (fieldNginxProcess == null || !fieldNginxProcess.isRunning()) { fieldNginxProcess = new LongRunningProcess(); fieldNginxProcess.setExecutorManager(getExec().getExecutorManager()); try { ArrayList args = new ArrayList(); Page config = getMediaArchive().getPageManager() .getPage("/" + getCatalogId() + "/streaming/streaming.conf"); if (fieldLocal) { config = getMediaArchive().getPageManager() .getPage("/" + getCatalogId() + "/streaming/streaming-local.conf"); } Page nginx = getMediaArchive().getPageManager().getPage("/" + getCatalogId() + "/streaming/nginx"); if (fieldLocal) { nginx = getMediaArchive().getPageManager().getPage("/" + getCatalogId() + "/streaming/nginx-local"); } File file = new File(nginx.getContentItem().getAbsolutePath()); Set perms = new HashSet<>(); perms.add(PosixFilePermission.OWNER_READ); perms.add(PosixFilePermission.OWNER_WRITE); perms.add(PosixFilePermission.OWNER_EXECUTE); Files.setPosixFilePermissions(file.toPath(), perms); args.add("-c"); args.add(config.getContentItem().getAbsolutePath()); fieldNginxProcess.start(nginx.getContentItem().getAbsolutePath(), args); // fieldNginxProcess.start("/usr/sbin/nginx", args); } catch (Exception ex) { fieldNginxProcess = null; throw new OpenEditException(ex); } } if (!fieldNginxProcess.isRunning()) { log.info(fieldNginxProcess.getStreamcopy().getNextResult()); } } public LongRunningProcess getNginxProcess() { return fieldNginxProcess; } public void stopNginx() { if (getNginxProcess() != null) { getNginxProcess().destroyProcess(); } } public void closeVOD(Data inPlan) { TestBench bench = getTestBench(); PageManager pm = bench.getPageManager(); PathProcessor processor = new PathProcessor() { public void processFile(ContentItem inContent, User inUser) { String fullpath = inContent.getPath(); if (fullpath.contains("m3u8") && fullpath.contains(inPlan.getId() + "_")) { Page page = pm.getPage(fullpath); String pagecontent = page.getContent(); if (pagecontent.contains("#EXT-X-ENDLIST")) { return; } StringBuffer buffer = new StringBuffer(); buffer.append(pagecontent); buffer.append("#EXT-X-ENDLIST\n"); StringItem newcontent = new StringItem(fullpath, buffer.toString(), "UTF-8"); page.setContentItem(newcontent); pm.putPage(page); } } }; processor.setRecursive(true); processor.setRootPath(getStreamsRoot()); processor.setPageManager(bench.getPageManager()); // processor.setIncludeMatches("index.m3u8"); processor.process(); log.info("reindexed " + processor.getExecCount()); } protected String getStreamsRoot() { return "/streams"; // change this once we decide where, WEB-INF data somewhere } public Page createMP4(Data inData) { TestBench bench = getTestBench(); String planid = inData.getId(); Page m3page = bench.getPageManager().getPage(getStreamsRoot() + "/" + inData.getId() + "_hi/index.m3u8"); Page output = bench.getPageManager().getPage(getStreamsRoot() + "/" + inData.getId() + ".mp4"); if (output.exists()) { bench.getPageManager().removePage(output); } // ffmpeg -i http://.../playlist.m3u8 -c copy -bsf:a aac_adtstoasc output.mp4 ArrayList args = new ArrayList(); args.add("-i"); args.add(m3page.getContentItem().getAbsolutePath()); args.add("-c"); args.add("copy"); args.add("-bsf:a"); args.add("aac_adtstoasc"); args.add(output.getContentItem().getAbsolutePath()); ExecResult task = getExec().runExec("avconv", args); if (task.isRunOk()) { return output; } else { log.info(task.getStandardError()); log.info(task.getStandardOut()); } return null; } public Collection loadClosedCaptions(String inAssetId) { MediaArchive archive = getMediaArchive(); Searcher captionsearcher = archive.getSearcher("videotrack"); Asset asset = archive.getAsset(inAssetId); if (asset != null) { Collection tracks = captionsearcher.query().exact("assetid", asset.getId()).sort("sourcelang").search(); return tracks; } return null; // All languages } public void deleteStreamFiles(Data inPlan) { TestBench bench = getTestBench(); PageManager pm = bench.getPageManager(); PathProcessor processor = new PathProcessor() { public void processDir(ContentItem inContent) { String fullpath = inContent.getPath(); if (fullpath.contains(inPlan.getId() + "_")) { Page folder = pm.getPage(fullpath); pm.removePage(folder); } } @Override public void processFile(ContentItem inContent, User inUser) { // TODO Auto-generated method stub } }; processor.setRecursive(true); processor.setRootPath(getStreamsRoot()); processor.setPageManager(bench.getPageManager()); // processor.setIncludeMatches("index.m3u8"); processor.process(); log.info("reindexed " + processor.getExecCount()); Page rootfile = pm.getPage(getStreamsRoot() + "/" + inPlan.getId() + ".m3u8"); pm.removePage(rootfile); } public void startStreams(String inStreamid) { TestBench bench = getTestBench(); RtmpStreamer streamer = getStreams().get(inStreamid); if (streamer == null) { streamer = (RtmpStreamer) bench.getBean("rtmpStreamer"); streamer.setStreamId(inStreamid); } streamer.startStream(); } public void cleanupStreams(String inStreamid) { RtmpStreamer streamer = getStreams().get(inStreamid); streamer.endStream(); } }