[Red5devs] [red5 commit] r3105 - in java/server/branches/paulg_mp4/src/org/red5: logging server/net/rtmps server/util
codesite-noreply at google.com
codesite-noreply at google.com
Thu Sep 25 14:06:32 PDT 2008
Author: mondain
Date: Thu Sep 25 14:05:37 2008
New Revision: 3105
Added:
java/server/branches/paulg_mp4/src/org/red5/logging/
java/server/branches/paulg_mp4/src/org/red5/logging/ContextLoggingListener.java
java/server/branches/paulg_mp4/src/org/red5/logging/LoggerContextFilter.java
java/server/branches/paulg_mp4/src/org/red5/logging/LoggingContextSelector.java
java/server/branches/paulg_mp4/src/org/red5/logging/W3CAppender.java
java/server/branches/paulg_mp4/src/org/red5/server/net/rtmps/
java/server/branches/paulg_mp4/src/org/red5/server/net/rtmps/TomcatRTMPSLoader.java
java/server/branches/paulg_mp4/src/org/red5/server/util/
java/server/branches/paulg_mp4/src/org/red5/server/util/FileUtil.java
java/server/branches/paulg_mp4/src/org/red5/server/util/PropertyConverter.java
Log:
added "missing" files
Added:
java/server/branches/paulg_mp4/src/org/red5/logging/ContextLoggingListener.java
==============================================================================
--- (empty file)
+++
java/server/branches/paulg_mp4/src/org/red5/logging/ContextLoggingListener.java
Thu Sep 25 14:05:37 2008
@@ -0,0 +1,87 @@
+package org.red5.logging;
+
+import java.util.List;
+
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+
+import org.slf4j.Logger;
+import org.slf4j.impl.StaticLoggerBinder;
+
+import ch.qos.logback.classic.LoggerContext;
+import ch.qos.logback.classic.selector.ContextSelector;
+
+/**
+ * A servlet context listener that puts this contexts LoggerContext
+ * into a static map of logger contexts within an overall singleton
+ * log context selector.
+ *
+ * To use it, add the following line to a web.xml file
+ *<pre>
+ <listener>
+
<listener-class>org.red5.logging.ContextLoggingListener</listener-class>
+ </listener>
+ *</pre>
+ *
+ * @author Paul Gregoire (mondain at gmail.com)
+ */
+public class ContextLoggingListener implements ServletContextListener {
+
+ public void contextDestroyed(ServletContextEvent event) {
+ System.out.println("Context destroying...");
+
+ String contextName = pathToName(event);
+ System.out.printf("About to detach context named %s\n", contextName);
+
+ ContextSelector selector =
StaticLoggerBinder.SINGLETON.getContextSelector();
+ LoggerContext context = selector.detachLoggerContext(contextName);
+ if (context != null) {
+ Logger logger = context.getLogger(LoggerContext.ROOT_NAME);
+ logger.info("Shutting down context {}", contextName);
+ context.shutdownAndReset();
+ } else {
+ System.err.printf("No context named %s was found", contextName);
+ }
+ }
+
+ public void contextInitialized(ServletContextEvent event) {
+ System.out.println("Context init...");
+
+ String contextName = pathToName(event);
+ System.out.printf("Logger name for context: %s\n", contextName);
+
+ try {
+ LoggingContextSelector selector = (LoggingContextSelector)
StaticLoggerBinder.SINGLETON.getContextSelector();
+
+ selector.setContextName(contextName);
+ LoggerContext context = selector.getLoggerContext();
+
+ if (context != null) {
+ Logger logger = context.getLogger(LoggerContext.ROOT_NAME);
+ logger.info("Starting up context {}", contextName);
+ } else {
+ System.err.printf("No context named %s was found", contextName);
+ }
+
+ List<String> ctxNameList = selector.getContextNames();
+ for (String s : ctxNameList) {
+ System.out.printf("Selector context name: %s\n", s);
+ }
+
+ } catch (Exception e) {
+ System.err.println("LoggingContextSelector is not the correct type");
+ e.printStackTrace();
+ }
+
+ }
+
+ private String pathToName(ServletContextEvent event) {
+ String contextName = event.getServletContext().getContextPath()
+ .replaceAll("/", "");
+ if (contextName.equals("")) {
+ contextName = "root";
+ }
+ return contextName;
+ }
+
+}
Added:
java/server/branches/paulg_mp4/src/org/red5/logging/LoggerContextFilter.java
==============================================================================
--- (empty file)
+++
java/server/branches/paulg_mp4/src/org/red5/logging/LoggerContextFilter.java
Thu Sep 25 14:05:37 2008
@@ -0,0 +1,79 @@
+package org.red5.logging;
+
+import java.io.IOException;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+import org.slf4j.LoggerFactory;
+import org.slf4j.impl.StaticLoggerBinder;
+
+import ch.qos.logback.classic.LoggerContext;
+
+/**
+ * A servlet filter that puts this contexts LoggerContext into a
Threadlocal variable.
+ *
+ * It removes it after the request is processed.
+ *
+ * To use it, add the following lines to a web.xml file
+ *<pre>
+ <filter>
+ <filter-name>LoggerContextFilter</filter-name>
+
<filter-class>org.red5.logging.LoggerContextFilter</filter-class>
+ </filter>
+ <filter-mapping>
+ <filter-name>LoggerContextFilter</filter-name>
+ <url-pattern>/*</url-pattern>
+ </filter-mapping>
+ *</pre>
+ *
+ * @author Paul Gregoire (mondain at gmail.com)
+ */
+public class LoggerContextFilter implements Filter {
+
+ private String contextName;
+
+ public void destroy() {
+ }
+
+ public void doFilter(ServletRequest request, ServletResponse response,
+ FilterChain chain) throws IOException, ServletException {
+
+ System.out.printf("Context name: %s\n", contextName);
+
+ LoggingContextSelector selector = (LoggingContextSelector)
StaticLoggerBinder.SINGLETON.getContextSelector();
+ System.out.println("Context select type: " +
selector.getClass().getName());
+
+ LoggerContext ctx = selector.getLoggerContext(contextName);
+
+ //load default logger context if its null
+ if (ctx == null) {
+ System.out.println("Logger context was null, getting default");
+ ctx = (LoggerContext) LoggerFactory.getILoggerFactory();
+ }
+
+ //evaluate context name against logger context name
+ if (!contextName.equals(ctx.getName())) {
+ System.err.printf("Logger context name and context name dont match
(%s != %s)\n", contextName, ctx.getName());
+ }
+
+ if (ctx != null) {
+ System.out.printf("Logger context name: %s\n", ctx.getName());
+ selector.setLocalContext(ctx);
+ }
+
+ try {
+ chain.doFilter(request, response);
+ } finally {
+ selector.removeLocalContext();
+ }
+ }
+
+ public void init(FilterConfig config) throws ServletException {
+ contextName =
config.getServletContext().getContextPath().replaceAll("/", "");
+ }
+}
Added:
java/server/branches/paulg_mp4/src/org/red5/logging/LoggingContextSelector.java
==============================================================================
--- (empty file)
+++
java/server/branches/paulg_mp4/src/org/red5/logging/LoggingContextSelector.java
Thu Sep 25 14:05:37 2008
@@ -0,0 +1,159 @@
+package org.red5.logging;
+
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import ch.qos.logback.classic.LoggerContext;
+import ch.qos.logback.classic.joran.JoranConfigurator;
+import ch.qos.logback.classic.selector.ContextSelector;
+import ch.qos.logback.classic.util.ContextInitializer;
+import ch.qos.logback.core.joran.spi.JoranException;
+import ch.qos.logback.core.util.Loader;
+import ch.qos.logback.core.util.StatusPrinter;
+
+/**
+ * A class that allows the LoggerFactory to access an web context based
LoggerContext.
+ *
+ * Add this java option
-Dlogback.ContextSelector=org.red5.logging.LoggingContextSelector
+ *
+ * @author Paul Gregoire (mondain at gmail.com)
+ */
+public class LoggingContextSelector implements ContextSelector {
+
+ private static final ConcurrentMap<String, LoggerContext> contextMap =
new ConcurrentHashMap<String, LoggerContext>();
+
+ private final ThreadLocal<LoggerContext> threadLocal = new
ThreadLocal<LoggerContext>();
+
+ private final LoggerContext defaultContext;
+
+ private String contextName;
+
+ private String contextConfigFile;
+
+ public LoggingContextSelector(LoggerContext context) {
+ System.out.printf("Setting default logging context: %s\n",
context.getName());
+ defaultContext = context;
+ }
+
+ public LoggerContext getLoggerContext() {
+ System.out.println("getLoggerContext request");
+ // First check if ThreadLocal has been set already
+ LoggerContext lc = threadLocal.get();
+ if (lc != null) {
+ System.out.printf("Thread local found: %s\n", lc.getName());
+ return lc;
+ }
+
+ if (contextName == null) {
+ System.out.println("Context name was null, returning default");
+ // We return the default context
+ return defaultContext;
+ } else {
+ // Let's see if we already know such a context
+ LoggerContext loggerContext = contextMap.get(contextName);
+ System.out.printf("Logger context for %s is %s\n", contextName,
loggerContext);
+
+ if (loggerContext == null) {
+ // We have to create a new LoggerContext
+ loggerContext = new LoggerContext();
+ loggerContext.setName(contextName);
+
+ if (contextConfigFile == null) {
+ contextConfigFile = String.format("logback-%s.xml", contextName);
+ System.out.printf("Context logger config file: %s\n",
contextConfigFile);
+ }
+
+ ClassLoader classloader =
Thread.currentThread().getContextClassLoader();
+ System.out.printf("Thread context cl: %s\n", classloader);
+ ClassLoader classloader2 = Loader.class.getClassLoader();
+ System.out.printf("Loader tcl: %s\n", classloader2);
+
+ //URL url = Loader.getResourceBySelfClassLoader(contextConfigFile);
+ URL url = Loader.getResource(contextConfigFile, classloader);
+ if (url != null) {
+ try {
+ JoranConfigurator configurator = new JoranConfigurator();
+ loggerContext.shutdownAndReset();
+ configurator.setContext(loggerContext);
+ configurator.doConfigure(url);
+ } catch (JoranException e) {
+ StatusPrinter.print(loggerContext);
+ }
+ } else {
+ try {
+ ContextInitializer ctxInit = new ContextInitializer(loggerContext);
+ ctxInit.autoConfig();
+ } catch (JoranException je) {
+ StatusPrinter.print(loggerContext);
+ }
+ }
+
+ System.out.printf("Adding logger context: %s to map for
context: %s\n", loggerContext.getName(), contextName);
+ contextMap.put(contextName, loggerContext);
+ }
+ return loggerContext;
+ }
+ }
+
+ public LoggerContext getLoggerContext(String name) {
+ System.out.printf("getLoggerContext request for %s\n", name);
+ System.out.printf("Context is in map: %s\n",
contextMap.containsKey(name));
+ return contextMap.get(name);
+ }
+
+ public LoggerContext getDefaultLoggerContext() {
+ return defaultContext;
+ }
+
+ public void attachLoggerContext(String contextName,
+ LoggerContext loggerContext) {
+ contextMap.put(contextName, loggerContext);
+ }
+
+ public LoggerContext detachLoggerContext(String loggerContextName) {
+ return contextMap.remove(loggerContextName);
+ }
+
+ public List<String> getContextNames() {
+ List<String> list = new ArrayList<String>();
+ list.addAll(contextMap.keySet());
+ return list;
+ }
+
+ public void setContextName(String contextName) {
+ this.contextName = contextName;
+ }
+
+ public void setContextConfigFile(String contextConfigFile) {
+ this.contextConfigFile = contextConfigFile;
+ }
+
+ /**
+ * Returns the number of managed contexts Used for testing purposes
+ *
+ * @return the number of managed contexts
+ */
+ public int getCount() {
+ return contextMap.size();
+ }
+
+ /**
+ * These methods are used by the LoggerContextFilter.
+ *
+ * They provide a way to tell the selector which context to use, thus
saving
+ * the cost of a JNDI call at each new request.
+ *
+ * @param context
+ */
+ public void setLocalContext(LoggerContext context) {
+ threadLocal.set(context);
+ }
+
+ public void removeLocalContext() {
+ threadLocal.remove();
+ }
+
+}
Added: java/server/branches/paulg_mp4/src/org/red5/logging/W3CAppender.java
==============================================================================
--- (empty file)
+++ java/server/branches/paulg_mp4/src/org/red5/logging/W3CAppender.java
Thu Sep 25 14:05:37 2008
@@ -0,0 +1,276 @@
+package org.red5.logging;
+
+/*
+ * RED5 Open Source Flash Server - http://www.osflash.org/red5
+ *
+ * Copyright (c) 2006-2008 by respective authors (see below). All rights
reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
under the
+ * terms of the GNU Lesser General Public License as published by the Free
Software
+ * Foundation; either version 2.1 of the License, or (at your option) any
later
+ * version.
+ *
+ * This library is distributed in the hope that it will be useful, but
WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
License along
+ * with this library; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import org.red5.server.api.Red5;
+
+import ch.qos.logback.classic.spi.LoggingEvent;
+import ch.qos.logback.core.FileAppender;
+import ch.qos.logback.core.status.ErrorStatus;
+
+/**
+ * Logback appender for the Extended W3C format.
+ *
+ * @see http://www.w3.org/TR/WD-logfile.html
+ * @author Paul Gregoire (mondain at gmail.com)
+ */
+public class W3CAppender extends FileAppender<LoggingEvent> {
+
+ /**
+ #Software: Red5 0.7.1
+ #Version: 1.0
+ #Date: 1998-11-19 22:48:39
+ #Fields: date time c-ip cs-username s-ip cs-method
+ */
+
+ //whether or not the header has been written
+ private static boolean headerWritten;
+
+ //events that are to be logged
+ private static String events;
+
+ //linked list to preserve order
+ private static List<String> eventsList = new ArrayList<String>();
+
+ //fields that are to be logged
+ private static String fields;
+
+ //linked list to preserve order
+ private static LinkedList<String> fieldList = new LinkedList<String>();
+
+ public W3CAppender() {
+ }
+
+ public void setEvents(String events) {
+ W3CAppender.events = events;
+ //make a list out of the event names
+ String[] arr = events.split(";");
+ for (String s : arr) {
+ eventsList.add(s);
+ }
+ }
+
+ public String getEvents() {
+ return events;
+ }
+
+ public void setFields(String fields) {
+ W3CAppender.fields = fields;
+ //make a list out of the field names
+ String[] arr = fields.split(";");
+ for (String s : arr) {
+ fieldList.add(s);
+ }
+ }
+
+ public String getFields() {
+ return fields;
+ }
+
+ @Override
+ public synchronized void doAppend(LoggingEvent event) {
+
+ //get the log message
+ String message = event.getFormattedMessage();
+
+ //look for w3c prefix
+ if (!message.startsWith("W3C")) {
+ return;
+ }
+
+ //
http://logback.qos.ch/apidocs/ch/qos/logback/classic/spi/LoggingEvent.html
+ StringBuilder sbuf = new StringBuilder(128);
+
+ //see if header has been written
+ if (!headerWritten) {
+ //build the header
+ StringBuilder sb = new StringBuilder("#Software: ");
+ sb.append(Red5.VERSION);
+ sb.append("\n#Version: 1.0");
+ sb.append("\n#Date: ");
+ sb.append(new Date());
+ sb.append("\n#Fields: ");
+ for (String field : fields.split(";")) {
+ sb.append(field);
+ sb.append(' ');
+ }
+ sb.append("\n");
+ //String header = sb.toString();
+ //System.out.print(header);
+ sbuf.append(sb.toString());
+ headerWritten = true;
+ sb = null;
+ }
+
+ //break the message into pieces
+ String[] arr = message.split(" ");
+ //create a map
+ Map<String, String> elements = new HashMap<String, String>(arr.length);
+ int i = 0;
+ for (String s : arr) {
+ if ((i = s.indexOf(':')) != -1) {
+ String key = s.substring(0, i);
+ String value = s.substring(i + 1);
+ //System.out.println("Key: " + key + " Value: " + value);
+ elements.put(key, value);
+ }
+ }
+
+ //Events Categories
+ //connect-pending session
+ //connect session
+ //disconnect session
+ //publish stream
+ //unpublish stream
+ //play stream
+ //pause stream
+ //unpause stream
+ //seek stream
+ //stop stream
+ //record stream
+ //recordstop stream
+ //server-start server
+ //server-stop server
+ //vhost-start vhost
+ //vhost-stop vhost
+ //app-start application
+ //app-stop application
+
+ //filter based on event type - asterik allows all events
+ if (!events.equals("*")) {
+ if (!eventsList.contains(elements.get("x-event"))) {
+ //System.err.println("Filtered out -
event: "+elements.get("x-event")+" event list: "+eventsList);
+ elements.clear();
+ elements = null;
+ sbuf = null;
+ return;
+ }
+ }
+
+ //x-category event category
+ //x-event type of event
+ //date date at which the event occurred
+ //time time at which the event occurred
+ //tz time zone information
+ //x-ctx event dependant context information
+ //s-ip ip address[es] of the server
+ //x-pid server process id
+ //x-cpu-load cpu load
+ //x-mem-load memory load (as reported in getServerStats)
+ //x-adaptor adaptor name
+ //x-vhost vhost name
+ //x-app application name
+ //x-appinst application instance name
+ //x-duration duration of an event/session
+ //x-status status code
+ //c-ip client ip address
+ //c-proto connection protocol - rtmp or rtmpt
+ //s-uri uri of the fms application
+ //cs-uri-stem stem of s-uri
+ //cs-uri-query query portion of s-uri
+ //c-referrer uri of the referrer
+ //c-user-agent user agent
+ //c-client-id client id
+ //cs-bytes bytes transferred from client to server
+ //sc-bytes bytes transferred from server to client
+ //c-connect-type type of connection received by the server
+ //x-sname stream name
+ //x-sname-query query portion of stream uri
+ //x-suri-query same as x-sname-query
+ //x-suri-stem cs-uri-stem + x-sname + x-file-ext
+ //x-suri x-suri-stem + x-suri-query
+ //x-file-name full file path of recorded stream
+ //x-file-ext stream type (flv or mp3)
+ //x-file-size stream size in bytes
+ //x-file-length stream length in seconds
+ //x-spos stream position
+ //cs-stream-bytes stream bytes transferred from client to server
+ //sc-stream-bytes stream bytes transferred from server to client
+ //x-service-name name of the service providing the connection
+ //x-sc-qos-bytes bytes transferred from server to client for quality of
service
+ //x-comment comments
+
+ //we may need date and/or time
+ Calendar cal = GregorianCalendar.getInstance();
+ cal.clear();
+ cal.setTimeInMillis(event.getTimeStamp());
+
+ //loop through the field names and grab the values from the map
+ //fields without a value get a tab character as a place holder if
+ //their value is not available to the server
+ for (String field : fieldList) {
+ String value = elements.get(field);
+ if (value == null) {
+ if ("date".equals(field)) {
+ sbuf.append(cal.get(Calendar.MONTH) + 1);
+ sbuf.append('/');
+ sbuf.append(cal.get(Calendar.DAY_OF_MONTH));
+ sbuf.append('/');
+ sbuf.append(cal.get(Calendar.YEAR));
+ } else if ("time".equals(field)) {
+ sbuf.append(cal.get(Calendar.HOUR_OF_DAY));
+ sbuf.append(':');
+ int min = cal.get(Calendar.MINUTE);
+ if (min < 10) {
+ sbuf.append('0');
+ sbuf.append(min);
+ } else {
+ sbuf.append(min);
+ }
+ } else if ("s-ip".equals(field)) {
+ //where should we grab the server ip from?
+ sbuf.append("127.0.0.1");
+ } else if ("x-pid".equals(field)) {
+ //should we pass thread name?
+ sbuf.append(event.getThreadName());
+ } else {
+ sbuf.append('\t');
+ }
+ } else {
+ sbuf.append(value);
+ }
+ //space padded
+ sbuf.append(' ');
+ }
+
+ sbuf.append("\n");
+ //System.out.println(sbuf.toString());
+
+ try {
+ this.writer.write(sbuf.toString());
+ if (this.immediateFlush) {
+ this.writer.flush();
+ }
+ } catch (IOException ioe) {
+ addStatus(new ErrorStatus("IO failure in appender", this, ioe));
+ }
+ }
+
+}
Added:
java/server/branches/paulg_mp4/src/org/red5/server/net/rtmps/TomcatRTMPSLoader.java
==============================================================================
--- (empty file)
+++
java/server/branches/paulg_mp4/src/org/red5/server/net/rtmps/TomcatRTMPSLoader.java
Thu Sep 25 14:05:37 2008
@@ -0,0 +1,162 @@
+package org.red5.server.net.rtmps;
+
+/*
+ * RED5 Open Source Flash Server - http://www.osflash.org/red5
+ *
+ * Copyright (c) 2006-2008 by respective authors (see below). All rights
reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
under the
+ * terms of the GNU Lesser General Public License as published by the Free
Software
+ * Foundation; either version 2.1 of the License, or (at your option) any
later
+ * version.
+ *
+ * This library is distributed in the hope that it will be useful, but
WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
License along
+ * with this library; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+import java.io.File;
+import org.apache.catalina.Context;
+import org.apache.catalina.Engine;
+import org.apache.catalina.Loader;
+import org.apache.catalina.Server;
+import org.apache.catalina.Valve;
+import org.apache.catalina.core.AprLifecycleListener;
+import org.apache.catalina.core.StandardHost;
+import org.apache.catalina.core.StandardWrapper;
+import org.apache.catalina.loader.WebappLoader;
+import org.red5.server.api.IServer;
+import org.red5.server.net.rtmpt.TomcatRTMPTLoader;
+import org.red5.server.util.FileUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Loader for the RTMPS server which uses Tomcat.
+ *
+ * @author The Red5 Project (red5 at osflash.org)
+ * @author Paul Gregoire (mondain at gmail.com)
+ */
+public class TomcatRTMPSLoader extends TomcatRTMPTLoader {
+
+ // Initialize Logging
+ private static Logger log =
LoggerFactory.getLogger(TomcatRTMPSLoader.class);
+
+ /**
+ * RTMPS Tomcat engine.
+ */
+ protected Engine rtmpsEngine;
+
+ /**
+ * Setter for server
+ *
+ * @param server
+ * Value to set for property 'server'.
+ */
+ public void setServer(IServer server) {
+ log.debug("RTMPS setServer");
+ this.server = server;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void init() {
+ log.info("Loading RTMPS context");
+
+ ClassLoader classloader = Thread.currentThread().getContextClassLoader();
+
+ rtmpsEngine = embedded.createEngine();
+ rtmpsEngine.setDefaultHost(host.getName());
+ rtmpsEngine.setName("red5RTMPSEngine");
+ rtmpsEngine.setParentClassLoader(classloader);
+
+ host.setParentClassLoader(classloader);
+
+ // add the valves to the host
+ for (Valve valve : valves) {
+ log.debug("Adding host valve: {}", valve);
+ ((StandardHost) host).addValve(valve);
+ }
+
+ // create and add root context
+ File appDirBase = new File(webappFolder);
+ String webappContextDir =
FileUtil.formatPath(appDirBase.getAbsolutePath(), "/root");
+ Context ctx = embedded.createContext("/", webappContextDir);
+ ctx.setReloadable(false);
+ log.debug("Context name: {}", ctx.getName());
+ Object ldr = ctx.getLoader();
+ if (ldr != null) {
+ if (ldr instanceof WebappLoader) {
+ log.debug("Replacing context loader");
+ ((WebappLoader)
ldr).setLoaderClass("org.red5.server.tomcat.WebappClassLoader");
+ } else {
+ log.debug("Context loader was instance of {}",
ldr.getClass().getName());
+ }
+ } else {
+ log.debug("Context loader was null");
+ WebappLoader wldr = new WebappLoader(classloader);
+ wldr.setLoaderClass("org.red5.server.tomcat.WebappClassLoader");
+ ctx.setLoader(wldr);
+ }
+ appDirBase = null;
+ webappContextDir = null;
+
+ host.addChild(ctx);
+
+ // add servlet wrapper
+ StandardWrapper wrapper = new StandardWrapper();
+ wrapper.setServletName("RTMPTServlet");
+ wrapper.setServletClass("org.red5.server.net.servlet.RTMPTServlet");
+ ctx.addChild(wrapper);
+
+ // add servlet mappings
+ ctx.addServletMapping("/open/*", "RTMPTServlet");
+ ctx.addServletMapping("/close/*", "RTMPTServlet");
+ ctx.addServletMapping("/send/*", "RTMPTServlet");
+ ctx.addServletMapping("/idle/*", "RTMPTServlet");
+
+ // add the host
+ rtmpsEngine.addChild(host);
+
+ // add new Engine to set of Engine for embedded server
+ embedded.addEngine(rtmpsEngine);
+
+ //turn off native apr support
+ AprLifecycleListener listener = new AprLifecycleListener();
+ listener.setSSLEngine("off");
+ connector.addLifecycleListener(listener);
+
+ log.debug("Protocol handler: {}",
connector.getProtocolHandlerClassName());
+
+ // set connection properties
+ connector.setSecure(true);
+ connector.setScheme("https");
+
+ // set other connection / protocol handler properties
+ for (String key : connectionProperties.keySet()) {
+ log.debug("Setting connection property: {} = {}", key,
connectionProperties.get(key));
+ connector.setProperty(key, connectionProperties.get(key));
+ }
+
+ // add new Connector to set of Connectors for embedded server,
+ // associated with Engine
+ embedded.addConnector(connector);
+
+ // start server
+ try {
+ log.info("Starting RTMPS engine");
+ //embedded.start();
+ connector.start();
+ } catch (Exception e) {
+ log.error("Error loading tomcat", e);
+ } finally {
+ registerJMX();
+ }
+
+ }
+
+}
Added: java/server/branches/paulg_mp4/src/org/red5/server/util/FileUtil.java
==============================================================================
--- (empty file)
+++ java/server/branches/paulg_mp4/src/org/red5/server/util/FileUtil.java
Thu Sep 25 14:05:37 2008
@@ -0,0 +1,504 @@
+package org.red5.server.util;
+
+/*
+ * RED5 Open Source Flash Server - http://www.osflash.org/red5
+ *
+ * Copyright (c) 2006-2008 by respective authors (see below). All rights
reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
under the
+ * terms of the GNU Lesser General Public License as published by the Free
Software
+ * Foundation; either version 2.1 of the License, or (at your option) any
later
+ * version.
+ *
+ * This library is distributed in the hope that it will be useful, but
WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
License along
+ * with this library; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.MappedByteBuffer;
+import java.nio.channels.FileChannel;
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Generic file utility containing useful file or directory
+ * manipulation functions.
+ *
+ * @author Paul Gregoire (mondain at gmail.com)
+ */
+public class FileUtil {
+
+ private static Logger log = LoggerFactory.getLogger(FileUtil.class);
+
+ // Random number generator used by name generation
+ private static Random random;
+
+ public static void copyFile(File source, File dest) throws IOException {
+ log.debug("Copy from {} to {}", source.getAbsoluteFile(), dest
+ .getAbsoluteFile());
+ FileInputStream fi = new FileInputStream(source);
+ FileChannel fic = fi.getChannel();
+ MappedByteBuffer mbuf = fic.map(FileChannel.MapMode.READ_ONLY, 0,
+ source.length());
+ fic.close();
+ fi.close();
+ fi = null;
+
+ // ensure the destination directory exists
+ if (!dest.exists()) {
+ String destPath = dest.getPath();
+ log.debug("Destination path: {}", destPath);
+ String destDir = destPath.substring(0, destPath
+ .lastIndexOf(File.separatorChar));
+ log.debug("Destination dir: {}", destDir);
+ File dir = new File(destDir);
+ if (!dir.exists()) {
+ if (dir.mkdirs()) {
+ log.debug("Directory created");
+ } else {
+ log.warn("Directory not created");
+ }
+ }
+ dir = null;
+ }
+
+ FileOutputStream fo = new FileOutputStream(dest);
+ FileChannel foc = fo.getChannel();
+ foc.write(mbuf);
+ foc.close();
+ fo.close();
+ fo = null;
+
+ mbuf.clear();
+ mbuf = null;
+ }
+
+ public static void copyFile(String source, String dest) throws
IOException {
+ copyFile(new File(source), new File(dest));
+ }
+
+ public static void moveFile(String source, String dest) throws
IOException {
+ copyFile(source, dest);
+ File src = new File(source);
+ if (src.exists() && src.canRead()) {
+ if (src.delete()) {
+ log.debug("Source file was deleted");
+ } else {
+ log
+ .debug("Source file was not deleted, the file will be deleted on
exit");
+ src.deleteOnExit();
+ }
+ } else {
+ log.warn("Source file could not be accessed for removal");
+ }
+ src = null;
+ }
+
+ /**
+ * Deletes a directory and its contents. This will fail if there are any
+ * file locks or if the directory cannot be emptied.
+ *
+ * @param directory
+ * @throws IOException
+ */
+ public static boolean deleteDirectory(String directory) throws
IOException {
+ return deleteDirectory(directory, false);
+ }
+
+ /**
+ * Deletes a directory and its contents. This will fail if there are any
+ * file locks or if the directory cannot be emptied.
+ *
+ * @param directory
+ * @param useOSNativeDelete
+ * flag to signify use of operating system delete function
+ * @throws IOException
+ */
+ public static boolean deleteDirectory(String directory,
+ boolean useOSNativeDelete) throws IOException {
+ boolean result = false;
+ if (!useOSNativeDelete) {
+ File dir = new File(directory);
+ // first all files have to be cleared out
+ for (File file : dir.listFiles()) {
+ if (file.delete()) {
+ log.debug("{} was deleted", file.getName());
+ } else {
+ log.debug("{} was not deleted", file.getName());
+ file.deleteOnExit();
+ }
+ file = null;
+ }
+ // not you may remove the dir
+ if (dir.delete()) {
+ log.debug("Directory was deleted");
+ result = true;
+ } else {
+ log.debug("Directory was not deleted, it may be deleted on exit");
+ dir.deleteOnExit();
+ }
+ dir = null;
+ } else {
+ Process p = null;
+ Thread std = null;
+ try {
+ Runtime runTime = Runtime.getRuntime();
+ log.debug("Execute runtime");
+ //determine file system type
+ if (File.separatorChar == '\\') {
+ //we are windows
+ p = runTime.exec("CMD /D /C \"RMDIR /Q /S "
+ + directory.replace('/', '\\') + "\"");
+ } else {
+ //we are unix variant
+ p = runTime.exec("rm -rf " + directory.replace('\\',
File.separatorChar));
+ }
+ // observe std out
+ std = stdOut(p);
+ // wait for the observer threads to finish
+ while (std.isAlive()) {
+ try {
+ Thread.sleep(250);
+ } catch (Exception e) {
+ }
+ }
+ log.debug("Process threads wait exited");
+ result = true;
+ } catch (Exception e) {
+ log.error("Error running delete script", e);
+ } finally {
+ if (null != p) {
+ log.debug("Destroying process");
+ p.destroy();
+ p = null;
+ }
+ std = null;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Rename a file natively; using REN on Windows and mv on *nix.
+ *
+ * @param from
+ * @param to
+ */
+ public static void rename(String from, String to) {
+ Process p = null;
+ Thread std = null;
+ try {
+ Runtime runTime = Runtime.getRuntime();
+ log.debug("Execute runtime");
+ //determine file system type
+ if (File.separatorChar == '\\') {
+ //we are windows
+ p = runTime.exec("CMD /D /C \"REN " + from + ' ' + to + "\"");
+ } else {
+ //we are unix variant
+ p = runTime.exec("mv -f " + from + ' ' + to);
+ }
+ // observe std out
+ std = stdOut(p);
+ // wait for the observer threads to finish
+ while (std.isAlive()) {
+ try {
+ Thread.sleep(250);
+ } catch (Exception e) {
+ }
+ }
+ log.debug("Process threads wait exited");
+ } catch (Exception e) {
+ log.error("Error running delete script", e);
+ } finally {
+ if (null != p) {
+ log.debug("Destroying process");
+ p.destroy();
+ p = null;
+ std = null;
+ }
+ }
+ }
+
+ /**
+ * Special method for capture of StdOut.
+ *
+ * @return
+ */
+ private final static Thread stdOut(final Process p) {
+ final byte[] empty = new byte[128];
+ for (int b = 0; b < empty.length; b++) {
+ empty[b] = (byte) 0;
+ }
+ Thread std = new Thread() {
+ public void run() {
+ StringBuilder sb = new StringBuilder(1024);
+ byte[] buf = new byte[128];
+ BufferedInputStream bis = new BufferedInputStream(p
+ .getInputStream());
+ log.debug("Process output:");
+ try {
+ while (bis.read(buf) != -1) {
+ sb.append(new String(buf).trim());
+ // clear buffer
+ System.arraycopy(empty, 0, buf, 0, buf.length);
+ }
+ log.debug(sb.toString());
+ bis.close();
+ } catch (Exception e) {
+ log.error("{}", e);
+ }
+ }
+ };
+ std.setDaemon(true);
+ std.start();
+ return std;
+ }
+
+ /**
+ * Create a directory.
+ *
+ * @param directory
+ * @return
+ * @throws IOException
+ */
+ public static boolean makeDirectory(String directory) throws IOException {
+ return makeDirectory(directory, false);
+ }
+
+ /**
+ * Create a directory. The parent directories will be created if
+ * <i>createParents</i> is passed as true.
+ *
+ * @param directory
+ * @param createParents
+ * @return
+ * @throws IOException
+ */
+ public static boolean makeDirectory(String directory, boolean
createParents)
+ throws IOException {
+ boolean created = false;
+ File dir = new File(directory);
+ if (createParents) {
+ created = dir.mkdirs();
+ if (created) {
+ log.debug("Directory created: {}", dir.getAbsolutePath());
+ } else {
+ log.debug("Directory was not created: {}", dir
+ .getAbsolutePath());
+ }
+ } else {
+ created = dir.mkdir();
+ if (created) {
+ log.debug("Directory created: {}", dir.getAbsolutePath());
+ } else {
+ log.debug("Directory was not created: {}", dir
+ .getAbsolutePath());
+ }
+ }
+ dir = null;
+ return created;
+ }
+
+ /**
+ * Unzips a given archive to a specified destination directory.
+ *
+ * @param compressedFileName
+ * @param destinationDir
+ */
+ public static void unzip(String compressedFileName, String
destinationDir) {
+
+ //strip everything except the applications name
+ String dirName = compressedFileName.substring(0,
compressedFileName.indexOf('-'));
+ //String tmpDir = System.getProperty("java.io.tmpdir");
+ File zipDir = new File(compressedFileName);
+ File parent = zipDir.getParentFile();
+ //File tmpDir = new File(System.getProperty("java.io.tmpdir"), dirName);
+ File tmpDir = new File(destinationDir);
+
+ // make the war directory
+ log.debug("Making directory: {}", tmpDir.mkdirs());
+
+ try {
+ ZipFile zf = new ZipFile(compressedFileName);
+ Enumeration e = zf.entries();
+ while(e.hasMoreElements()) {
+ ZipEntry ze = (ZipEntry) e.nextElement();
+ log.debug("Unzipping {}", ze.getName());
+ if(ze.isDirectory()) {
+ log.debug("is a directory");
+ File dir = new File(tmpDir + "/" + ze.getName());
+ Boolean tmp = dir.mkdir();
+ log.debug("{}", tmp);
+ continue;
+ }
+ FileOutputStream fout = new FileOutputStream(tmpDir + "/" +
ze.getName());
+ InputStream in = zf.getInputStream(ze);
+ copy(in, fout);
+ in.close();
+ fout.close();
+ }
+ e = null;
+ } catch (IOException e) {
+ log.debug("Error unzipping", e);
+ e.printStackTrace();
+ }
+ }
+
+ public static void copy(InputStream in, OutputStream out) throws
IOException {
+ synchronized(in) {
+ synchronized(out) {
+ byte[] buffer = new byte[256];
+ while (true) {
+ int bytesRead = in.read(buffer);
+ if(bytesRead == -1) break;
+ out.write(buffer, 0, bytesRead);
+ }
+ }
+ }
+ }
+
+ /**
+ * Unzips a given archive to a specified destination directory.
+ *
+ * @param compressedFileName
+ * @param destinationDir
+ */
+// public static void unzip(String compressedFileName, String
destinationDir) {
+// log.debug("Unzip - file: {} destination: {}", compressedFileName,
destinationDir);
+// try {
+// final int BUFFER = 2048;
+// BufferedOutputStream dest = null;
+// FileInputStream fis = new FileInputStream(compressedFileName);
+// CheckedInputStream checksum = new CheckedInputStream(fis,
+// new Adler32());
+// ZipInputStream zis = new ZipInputStream(new BufferedInputStream(
+// checksum));
+// ZipEntry entry;
+// while ((entry = zis.getNextEntry()) != null) {
+// log.debug("Extracting: {}", entry);
+// String name = entry.getName();
+// int count;
+// byte data[] = new byte[BUFFER];
+// // write the files to the disk
+// File destFile = new File(destinationDir, name);
+// log.debug("Absolute path: {}", destFile.getAbsolutePath());
+// //create dirs as needed, look for file extension to determine type
+// if (entry.isDirectory()) {
+// log.debug("Entry is detected as a directory");
+// if (destFile.mkdirs()) {
+// log.debug("Directory created: {}", destFile.getName());
+// } else {
+// log.warn("Directory was not created: {}", destFile.getName());
+// }
+// destFile = null;
+// continue;
+// }
+//
+// FileOutputStream fos = new FileOutputStream(destFile);
+// dest = new BufferedOutputStream(fos, BUFFER);
+// while ((count = zis.read(data, 0, BUFFER)) != -1) {
+// dest.write(data, 0, count);
+// }
+// dest.flush();
+// dest.close();
+// destFile = null;
+// }
+// zis.close();
+// log.debug("Checksum: {}", checksum.getChecksum().getValue());
+// } catch (Exception e) {
+// log.error("Error unzipping {}", compressedFileName, e);
+// e.printStackTrace();
+// }
+//
+// }
+
+ /**
+ * Quick-n-dirty directory formatting to support launching in windows,
specifically from ant.
+ */
+ public static String formatPath(String absWebappsPath, String
contextDirName) {
+ StringBuilder path = new StringBuilder(absWebappsPath.length() +
contextDirName.length());
+ path.append(absWebappsPath);
+ if (log.isDebugEnabled()) {
+ log.debug("Path start: {}", path.toString());
+ }
+ int idx = -1;
+ if (File.separatorChar != '/') {
+ while ((idx = path.indexOf(File.separator)) != -1) {
+ path.deleteCharAt(idx);
+ path.insert(idx, '/');
+ }
+ }
+ if (log.isDebugEnabled()) {
+ log.debug("Path step 1: {}", path.toString());
+ }
+ //remove any './'
+ if ((idx = path.indexOf("./")) != -1) {
+ path.delete(idx, idx + 2);
+ }
+ if (log.isDebugEnabled()) {
+ log.debug("Path step 2: {}", path.toString());
+ }
+ //add / to base path if one doesnt exist
+ if (path.charAt(path.length() - 1) != '/') {
+ path.append('/');
+ }
+ if (log.isDebugEnabled()) {
+ log.debug("Path step 3: {}", path.toString());
+ }
+ //remove the / from the beginning of the context dir
+ if (contextDirName.charAt(0) == '/' && path.charAt(path.length() -
1) == '/') {
+ path.append(contextDirName.substring(1));
+ } else {
+ path.append(contextDirName);
+ }
+ if (log.isDebugEnabled()) {
+ log.debug("Path step 4: {}", path.toString());
+ }
+ return path.toString();
+ }
+
+ /**
+ * Generates a custom name containing numbers and an underscore ex.
282818_00023.
+ * The name contains current seconds and a random number component.
+ *
+ * @return
+ */
+ public static String generateCustomName() {
+ if (random == null) {
+ random = new Random();
+ }
+ StringBuilder sb = new StringBuilder();
+ sb.append(PropertyConverter.getCurrentTimeSeconds());
+ sb.append('_');
+ int i = random.nextInt(99999);
+ if (i < 10) {
+ sb.append("0000");
+ } else if (i < 100) {
+ sb.append("000");
+ } else if (i < 1000) {
+ sb.append("00");
+ } else if (i < 10000) {
+ sb.append("0");
+ }
+ sb.append(i);
+ return sb.toString();
+ }
+
+}
Added:
java/server/branches/paulg_mp4/src/org/red5/server/util/PropertyConverter.java
==============================================================================
--- (empty file)
+++
java/server/branches/paulg_mp4/src/org/red5/server/util/PropertyConverter.java
Thu Sep 25 14:05:37 2008
@@ -0,0 +1,162 @@
+package org.red5.server.util;
+
+/*
+ * RED5 Open Source Flash Server - http://www.osflash.org/red5
+ *
+ * Copyright (c) 2006-2008 by respective authors (see below). All rights
reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
under the
+ * terms of the GNU Lesser General Public License as published by the Free
Software
+ * Foundation; either version 2.1 of the License, or (at your option) any
later
+ * version.
+ *
+ * This library is distributed in the hope that it will be useful, but
WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
License along
+ * with this library; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+import java.util.Calendar;
+
+import org.apache.commons.lang.StringUtils;
+
+/**
+ * Converter for properties originating from properties files.
Predetermined
+ * string formats are converted into other usable types such as timestamps.
+ *
+ * @author Paul Gregoire (mondain at gmail.com)
+ */
+public class PropertyConverter {
+
+ /**
+ * Converts a string denoting an amount of time into milliseconds and adds
+ * it to the current date. Strings are expected to follow this form where
#
+ * equals a digit: #M The following are permitted for denoting time: H =
+ * hours, M = minutes, S = seconds
+ *
+ * @param time
+ * @return time in milliseconds
+ */
+ public static long convertStringToFutureTimeMillis(String time) {
+ Calendar exp = Calendar.getInstance();
+ if (time.endsWith("H")) {
+ exp.add(Calendar.HOUR, Integer.valueOf(StringUtils
+ .remove(time, 'H')));
+ } else if (time.endsWith("M")) {
+ exp.add(Calendar.MINUTE, Integer.valueOf(StringUtils.remove(time,
+ 'M')));
+ } else if (time.endsWith("S")) {
+ exp.add(Calendar.MILLISECOND, Integer.valueOf(StringUtils.remove(
+ time, 'S')) * 1000);
+ }
+ return exp.getTimeInMillis();
+ }
+
+ /**
+ * Converts a string denoting an amount of time into seconds. Strings are
+ * expected to follow this form where # equals a digit: #M The following
are
+ * permitted for denoting time: H = hours, M = minutes, S = seconds
+ *
+ * @param time
+ * @return time in seconds
+ */
+ public static int convertStringToTimeSeconds(String time) {
+ int result = 0;
+ if (time.endsWith("H")) {
+ int hoursToAdd = Integer.valueOf(StringUtils.remove(time, 'H'));
+ result = (60 * 60) * hoursToAdd;
+ } else if (time.endsWith("M")) {
+ int minsToAdd = Integer.valueOf(StringUtils.remove(time, 'M'));
+ result = 60 * minsToAdd;
+ } else if (time.endsWith("S")) {
+ int secsToAdd = Integer.valueOf(StringUtils.remove(time, 'S'));
+ result = secsToAdd;
+ }
+ return result;
+ }
+
+ /**
+ * Converts a string denoting an amount of time into milliseconds. Strings
+ * are expected to follow this form where # equals a digit: #M The
following
+ * are permitted for denoting time: H = hours, M = minutes, S = seconds
+ *
+ * @param time
+ * @return time in milliseconds
+ */
+ public static long convertStringToTimeMillis(String time) {
+ long result = 0;
+ if (time.endsWith("H")) {
+ long hoursToAdd = Integer.valueOf(StringUtils.remove(time, 'H'));
+ result = ((1000 * 60) * 60) * hoursToAdd;
+ } else if (time.endsWith("M")) {
+ long minsToAdd = Integer.valueOf(StringUtils.remove(time, 'M'));
+ result = (1000 * 60) * minsToAdd;
+ } else if (time.endsWith("S")) {
+ long secsToAdd = Integer.valueOf(StringUtils.remove(time, 'S'));
+ result = 1000 * secsToAdd;
+ }
+ return result;
+ }
+
+ /**
+ * Converts a string denoting an amount of bytes into an integer value.
+ * Strings are expected to follow this form where # equals a digit: #M The
+ * following are permitted for denoting binary size: K = kilobytes, M =
+ * megabytes, G = gigabytes
+ *
+ * @param memSize
+ * @return size as an integer
+ */
+ public static int convertStringToMemorySizeInt(String memSize) {
+ int result = 0;
+ if (memSize.endsWith("K")) {
+ result = Integer.valueOf(StringUtils.remove(memSize, 'K')) * 1000;
+ } else if (memSize.endsWith("M")) {
+ result = Integer.valueOf(StringUtils.remove(memSize, 'M')) * 1000 *
1000;
+ } else if (memSize.endsWith("G")) {
+ result = Integer.valueOf(StringUtils.remove(memSize, 'G')) * 1000 *
1000 * 1000;
+ }
+ return result;
+ }
+
+ /**
+ * Converts a string denoting an amount of bytes into an long value.
Strings
+ * are expected to follow this form where # equals a digit: #M The
following
+ * are permitted for denoting binary size: K = kilobytes, M = megabytes,
G =
+ * gigabytes
+ *
+ * @param memSize
+ * @return size as an long
+ */
+ public static long convertStringToMemorySizeLong(String memSize) {
+ long result = 0;
+ if (memSize.endsWith("K")) {
+ result = Long.valueOf(StringUtils.remove(memSize, 'K')) * 1000;
+ } else if (memSize.endsWith("M")) {
+ result = Long.valueOf(StringUtils.remove(memSize, 'M')) * 1000 * 1000;
+ } else if (memSize.endsWith("G")) {
+ result = Long.valueOf(StringUtils.remove(memSize, 'G')) * 1000 * 1000 *
1000;
+ }
+ return result;
+ }
+
+ /**
+ * Quick time converter to keep our timestamps compatible with PHP's
time()
+ * (seconds)
+ */
+ public static Integer getCurrentTimeSeconds() {
+ return convertMillisToSeconds(System.currentTimeMillis());
+ }
+
+ /**
+ * Quick time converter to keep our timestamps compatible with PHP's
time()
+ * (seconds)
+ */
+ public static Integer convertMillisToSeconds(Long millis) {
+ return Long.valueOf(millis / 1000).intValue();
+ }
+
+}
More information about the Red5devs
mailing list