Package org.red5.server.stream

Source Code of org.red5.server.stream.ServerStream

/*
* RED5 Open Source Flash Server - http://code.google.com/p/red5/
*
* Copyright 2006-2014 by respective authors (see below). All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.red5.server.stream;

import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;

import org.apache.mina.core.buffer.IoBuffer;
import org.red5.codec.IAudioStreamCodec;
import org.red5.codec.IStreamCodecInfo;
import org.red5.codec.IVideoStreamCodec;
import org.red5.codec.StreamCodecInfo;
import org.red5.server.api.IContext;
import org.red5.server.api.scheduling.IScheduledJob;
import org.red5.server.api.scheduling.ISchedulingService;
import org.red5.server.api.scope.IScope;
import org.red5.server.api.stream.IPlayItem;
import org.red5.server.api.stream.IPlaylistController;
import org.red5.server.api.stream.IServerStream;
import org.red5.server.api.stream.IStreamAwareScopeHandler;
import org.red5.server.api.stream.IStreamListener;
import org.red5.server.api.stream.IStreamPacket;
import org.red5.server.api.stream.StreamState;
import org.red5.server.api.stream.support.SimplePlayItem;
import org.red5.server.messaging.IFilter;
import org.red5.server.messaging.IMessage;
import org.red5.server.messaging.IMessageComponent;
import org.red5.server.messaging.IMessageInput;
import org.red5.server.messaging.IMessageOutput;
import org.red5.server.messaging.IPassive;
import org.red5.server.messaging.IPipe;
import org.red5.server.messaging.IPipeConnectionListener;
import org.red5.server.messaging.IProvider;
import org.red5.server.messaging.IPushableConsumer;
import org.red5.server.messaging.OOBControlMessage;
import org.red5.server.messaging.PipeConnectionEvent;
import org.red5.server.net.rtmp.event.AudioData;
import org.red5.server.net.rtmp.event.IRTMPEvent;
import org.red5.server.net.rtmp.event.VideoData;
import org.red5.server.stream.message.RTMPMessage;
import org.red5.server.stream.message.ResetMessage;
import org.red5.server.util.ScopeUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* An implementation for server side stream.
*
* @author The Red5 Project
* @author Steven Gong (steven.gong@gmail.com)
* @author Paul Gregoire (mondain@gmail.com)
*/
public class ServerStream extends AbstractStream implements IServerStream, IFilter, IPushableConsumer, IPipeConnectionListener {
  private static final Logger log = LoggerFactory.getLogger(ServerStream.class);

  private static final long WAIT_THRESHOLD = 0;

  /**
   * Stream published name
   */
  protected String publishedName;

  /**
   * Actual playlist controller
   */
  protected IPlaylistController controller;

  /**
   * Default playlist controller
   */
  protected IPlaylistController defaultController;

  /**
   * Rewind flag state
   */
  private boolean isRewind;

  /**
   * Random flag state
   */
  private boolean isRandom;

  /**
   * Repeat flag state
   */
  private boolean isRepeat;

  /**
   * List of items in this playlist
   */
  protected CopyOnWriteArrayList<IPlayItem> items;

  /**
   * Current item index
   */
  private int currentItemIndex;

  /**
   * Current item
   */
  protected IPlayItem currentItem;

  /**
   * Message input
   */
  private IMessageInput msgIn;

  /**
   * Message output
   */
  private IMessageOutput msgOut;

  /**
   * Provider service
   */
  private IProviderService providerService;

  /**
   * Scheduling service
   */
  private ISchedulingService scheduler;

  /**
   * Live broadcasting scheduled job name
   */
  private volatile String liveJobName;

  /**
   * VOD scheduled job name
   */
  private volatile String vodJobName;

  /**
   * VOD start timestamp
   */
  private long vodStartTS;

  /**
   * Server start timestamp
   */
  private long serverStartTS;

  /**
   * Next msg's timestamp
   */
  private long nextTS;

  /**
   * Next RTMP message
   */
  private RTMPMessage nextRTMPMessage;

  /** Listeners to get notified about received packets. */
  private CopyOnWriteArraySet<IStreamListener> listeners = new CopyOnWriteArraySet<IStreamListener>();

  /**
   * Recording listener
   */
  private WeakReference<IRecordingListener> recordingListener; 
 
  /** Constructs a new ServerStream. */
  public ServerStream() {
    defaultController = new SimplePlaylistController();
    items = new CopyOnWriteArrayList<IPlayItem>();
  }

  /** {@inheritDoc} */
  public void addItem(IPlayItem item) {
    items.add(item);
  }

  /** {@inheritDoc} */
  public void addItem(IPlayItem item, int index) {
    IPlayItem prev = items.get(index);
    if (prev != null && prev instanceof SimplePlayItem) {
      // since it replaces the item in the current spot, reset the items time so the sort will work
      ((SimplePlayItem) item).setCreated(((SimplePlayItem) prev).getCreated() - 1);
    }
    items.add(index, item);
    if (index <= currentItemIndex) {
      // item was added before the currently playing
      currentItemIndex++;
    }
  }

  /** {@inheritDoc} */
  public void removeItem(int index) {
    if (index < 0 || index >= items.size()) {
      return;
    }
    items.remove(index);
    if (index < currentItemIndex) {
      // item was removed before the currently playing
      currentItemIndex--;
    } else if (index == currentItemIndex) {
      // TODO: the currently playing item is removed - this should be handled differently
      currentItemIndex--;
    }
  }

  /** {@inheritDoc} */
  public void removeAllItems() {
    currentItemIndex = 0;
    items.clear();
  }

  /** {@inheritDoc} */
  public int getItemSize() {
    return items.size();
  }

  public CopyOnWriteArrayList<IPlayItem> getItems() {
    return items;
  }

  /** {@inheritDoc} */
  public int getCurrentItemIndex() {
    return currentItemIndex;
  }

  /** {@inheritDoc} */
  public IPlayItem getCurrentItem() {
    return currentItem;
  }

  /** {@inheritDoc} */
  public IPlayItem getItem(int index) {
    try {
      return items.get(index);
    } catch (IndexOutOfBoundsException e) {
      return null;
    }
  }

  /** {@inheritDoc} */
  public void previousItem() {
    stop();
    moveToPrevious();
    if (currentItemIndex == -1) {
      return;
    }
    IPlayItem item = items.get(currentItemIndex);
    play(item);
  }

  /** {@inheritDoc} */
  public boolean hasMoreItems() {
    int nextItem = currentItemIndex + 1;
    if (nextItem >= items.size() && !isRepeat) {
      return false;
    } else {
      return true;
    }
  }

  /** {@inheritDoc} */
  public void nextItem() {
    stop();
    moveToNext();
    if (currentItemIndex == -1) {
      return;
    }
    IPlayItem item = items.get(currentItemIndex);
    play(item);
  }

  /** {@inheritDoc} */
  public void setItem(int index) {
    if (index < 0 || index >= items.size()) {
      return;
    }
    stop();
    currentItemIndex = index;
    IPlayItem item = items.get(currentItemIndex);
    play(item);
  }

  /** {@inheritDoc} */
  public boolean isRandom() {
    return isRandom;
  }

  /** {@inheritDoc} */
  public void setRandom(boolean random) {
    isRandom = random;
  }

  /** {@inheritDoc} */
  public boolean isRewind() {
    return isRewind;
  }

  /** {@inheritDoc} */
  public void setRewind(boolean rewind) {
    isRewind = rewind;
  }

  /** {@inheritDoc} */
  public boolean isRepeat() {
    return isRepeat;
  }

  /** {@inheritDoc} */
  public void setRepeat(boolean repeat) {
    isRepeat = repeat;
  }

  /** {@inheritDoc} */
  public void setPlaylistController(IPlaylistController controller) {
    this.controller = controller;
  }

  /** {@inheritDoc} */
  public void saveAs(String name, boolean isAppend) throws IOException {
    // one recording listener at a time via this entry point
    if (recordingListener == null) {
      IScope scope = getScope();
      // create a recording listener
      IRecordingListener listener = (IRecordingListener) ScopeUtils.getScopeService(scope, IRecordingListener.class, RecordingListener.class);
      // initialize the listener
      if (listener.init(scope, name, isAppend)) {
        // get decoder info if it exists for the stream
        IStreamCodecInfo codecInfo = getCodecInfo();
        log.debug("Codec info: {}", codecInfo);
        if (codecInfo instanceof StreamCodecInfo) {
          StreamCodecInfo info = (StreamCodecInfo) codecInfo;
          IVideoStreamCodec videoCodec = info.getVideoCodec();
          log.debug("Video codec: {}", videoCodec);
          if (videoCodec != null) {
            //check for decoder configuration to send
            IoBuffer config = videoCodec.getDecoderConfiguration();
            if (config != null) {
              log.debug("Decoder configuration is available for {}", videoCodec.getName());
              VideoData videoConf = new VideoData(config.asReadOnlyBuffer());
              try {
                log.debug("Setting decoder configuration for recording");
                listener.getFileConsumer().setVideoDecoderConfiguration(videoConf);
              } finally {
                videoConf.release();
              }
            }
          } else {
            log.debug("Could not initialize stream output, videoCodec is null.");
          }
          IAudioStreamCodec audioCodec = info.getAudioCodec();
          log.debug("Audio codec: {}", audioCodec);
          if (audioCodec != null) {
            //check for decoder configuration to send
            IoBuffer config = audioCodec.getDecoderConfiguration();
            if (config != null) {
              log.debug("Decoder configuration is available for {}", audioCodec.getName());
              AudioData audioConf = new AudioData(config.asReadOnlyBuffer());
              try {
                log.debug("Setting decoder configuration for recording");
                listener.getFileConsumer().setAudioDecoderConfiguration(audioConf);
              } finally {
                audioConf.release();
              }
            }
          } else {
            log.debug("No decoder configuration available, audioCodec is null.");
          }
        }
        // set as primary listener
        recordingListener = new WeakReference<IRecordingListener>(listener);
        // add as a listener
        addStreamListener(listener);
        // start the listener thread
        listener.start();
      } else {
        log.warn("Recording listener failed to initialize for stream: {}", name);
      }
    } else {
      log.info("Recording listener already exists for stream: {}", name);
    }
  }

  /** {@inheritDoc} */
  public String getSaveFilename() {
    if (recordingListener != null) {
      return recordingListener.get().getFileName();
    }
    return null;
  }

  /** {@inheritDoc} */
  public IProvider getProvider() {
    return this;
  }

  /** {@inheritDoc} */
  public String getPublishedName() {
    return publishedName;
  }

  /** {@inheritDoc} */
  public void setPublishedName(String name) {
    publishedName = name;
  }

  /**
   * Start this server-side stream
   */
  public void start() {
    if (state != StreamState.UNINIT) {
      throw new IllegalStateException("State " + state + " not valid to start");
    }
    if (items.size() == 0) {
      throw new IllegalStateException("At least one item should be specified to start");
    }
    if (publishedName == null) {
      throw new IllegalStateException("A published name is needed to start");
    }
    try {
      IScope scope = getScope();
      IContext context = scope.getContext();
      providerService = (IProviderService) context.getBean(IProviderService.BEAN_NAME);
      // publish this server-side stream
      providerService.registerBroadcastStream(scope, publishedName, this);
      scheduler = (ISchedulingService) context.getBean(ISchedulingService.BEAN_NAME);
    } catch (NullPointerException npe) {
      log.warn("Context beans were not available; this is ok during unit testing", npe);
    }
    setState(StreamState.STOPPED);
    currentItemIndex = -1;
    nextItem();
  }

  /**
   * Stop this server-side stream
   */
  public void stop() {
    if (state == StreamState.PLAYING || state == StreamState.PAUSED) {
      if (liveJobName != null) {
        scheduler.removeScheduledJob(liveJobName);
        liveJobName = null;
      }
      if (vodJobName != null) {
        scheduler.removeScheduledJob(vodJobName);
        vodJobName = null;
      }
      if (msgIn != null) {
        msgIn.unsubscribe(this);
        msgIn = null;
      }
      if (nextRTMPMessage != null) {
        nextRTMPMessage.getBody().release();
      }
      stopRecording();
      setState(StreamState.STOPPED);
    }
  }

  /**
   * Stops any currently active recording.
   */
  public void stopRecording() {
    IRecordingListener listener = null;
    if (recordingListener != null && (listener = recordingListener.get()).isRecording()) {
      notifyRecordingStop();
      // remove the listener
      removeStreamListener(listener);
      // stop the recording listener
      listener.stop();
      // clear and null-out the thread local
            recordingListener.clear();
            recordingListener = null;
    }
  } 
 
  /** {@inheritDoc} */
  @SuppressWarnings("incomplete-switch")
  public void pause() {
    switch (state) {
      case PLAYING:
        setState(StreamState.PAUSED);
        break;
      case PAUSED:
        setState(StreamState.PLAYING);
        vodStartTS = 0;
        serverStartTS = System.currentTimeMillis();
        scheduleNextMessage();
    }
  }

  /** {@inheritDoc} */
  public void seek(int position) {
    // seek only allowed when playing or paused
    if (state == StreamState.PLAYING || state == StreamState.PAUSED) {
      sendVODSeekCM(msgIn, position);
    }
  }

  /** {@inheritDoc} */
  public void close() {
    if (state == StreamState.PLAYING || state == StreamState.PAUSED) {
      stop();
    }
    if (msgOut != null) {
      msgOut.unsubscribe(this);
    }
    notifyBroadcastClose();
    setState(StreamState.CLOSED);
  }

  /** {@inheritDoc} */
  public void onOOBControlMessage(IMessageComponent source, IPipe pipe, OOBControlMessage oobCtrlMsg) {
  }

  /** {@inheritDoc} */
  public void pushMessage(IPipe pipe, IMessage message) throws IOException {
    pushMessage(message);
  }

  /**
   * Pipe connection event handler. There are two types of pipe connection events so far,
   * provider push connection event and provider disconnection event.
   *
   * Pipe events handling is the most common way of working with pipes.
   *
   * @param event        Pipe connection event context
   */
  public void onPipeConnectionEvent(PipeConnectionEvent event) {
    switch (event.getType()) {
      case PipeConnectionEvent.PROVIDER_CONNECT_PUSH:
        if (event.getProvider() == this && (event.getParamMap() == null || !event.getParamMap().containsKey("record"))) {
          this.msgOut = (IMessageOutput) event.getSource();
        }
        break;
      case PipeConnectionEvent.PROVIDER_DISCONNECT:
        if (this.msgOut == event.getSource()) {
          this.msgOut = null;
        }
        break;
      default:
        break;
    }
  }

  /**
   * Play a specific IPlayItem.
   * The strategy for now is VOD first, Live second.
   *
   * @param item        Item to play
   */
  protected void play(IPlayItem item) {
    // dont play unless we are stopped
    if (state == StreamState.STOPPED) {
      // assume this is not live stream
      boolean isLive = false;
      if (providerService != null) {
        msgIn = providerService.getVODProviderInput(getScope(), item.getName());
        if (msgIn == null) {
          msgIn = providerService.getLiveProviderInput(getScope(), item.getName(), true);
          isLive = true;
        }
        if (msgIn == null) {
          log.warn("ABNORMAL Can't get both VOD and Live input from providerService");
          return;
        }
      }
      setState(StreamState.PLAYING);
      currentItem = item;
      sendResetMessage();
      if (msgIn != null) {
        msgIn.subscribe(this, null);
      }
      if (isLive) {
        if (item.getLength() >= 0) {
          liveJobName = scheduler.addScheduledOnceJob(item.getLength(), new IScheduledJob() {
            public void execute(ISchedulingService service) {
              if (liveJobName == null) {
                return;
              }
              liveJobName = null;
              onItemEnd();
            }
          });
        }
      } else {
        long start = item.getStart();
        if (start < 0) {
          start = 0;
        }
        sendVODInitCM(msgIn, (int) start);
        startBroadcastVOD();
      }
    }
  }

  /**
   * Play next item on item end
   */
  protected void onItemEnd() {
    nextItem();
  }

  /**
   * Push message
   * @param message     Message
   */
  private void pushMessage(IMessage message) throws IOException {
    if (msgOut != null) {
      msgOut.pushMessage(message);
    }
    // Notify listeners about received packet
    if (message instanceof RTMPMessage) {
      final IRTMPEvent rtmpEvent = ((RTMPMessage) message).getBody();
      if (rtmpEvent instanceof IStreamPacket) {
        for (IStreamListener listener : getStreamListeners()) {
          try {
            listener.packetReceived(this, (IStreamPacket) rtmpEvent);
          } catch (Exception e) {
            log.error("Error while notifying listener " + listener, e);
          }
        }
      }
    }
  }

  /**
   * Send reset message
   */
  private void sendResetMessage() {
    // Send new reset message
    try {
      pushMessage(new ResetMessage());
    } catch (IOException err) {
      log.error("Error while sending reset message.", err);
    }
  }

  /**
   * Begin VOD broadcasting
   */
  protected void startBroadcastVOD() {
    nextRTMPMessage = null;
    vodStartTS = 0;
    serverStartTS = System.currentTimeMillis();
    IStreamAwareScopeHandler handler = getStreamAwareHandler();
    if (handler != null) {
      if (recordingListener != null && recordingListener.get().isRecording()) {
        // callback for record start
        handler.streamRecordStart(this);
      } else {
        // callback for publish start
        handler.streamPublishStart(this);
      }
    }
    notifyBroadcastStart();
    scheduleNextMessage();
  }

  /**
   *  Notifies handler on stream broadcast stop
   */
  protected void notifyBroadcastClose() {
    IStreamAwareScopeHandler handler = getStreamAwareHandler();
    if (handler != null) {
      try {
        handler.streamBroadcastClose(this);
      } catch (Throwable t) {
        log.error("error notify streamBroadcastStop", t);
      }
    }
  }

  /**
   *  Notifies handler on stream recording stop
   */
  private void notifyRecordingStop() {
    IStreamAwareScopeHandler handler = getStreamAwareHandler();
    if (handler != null) {
      try {
        handler.streamRecordStop(this);
      } catch (Throwable t) {
        log.error("Error in notifyBroadcastClose", t);
      }
    }
  } 
 
  /**
   *  Notifies handler on stream broadcast start
   */
  protected void notifyBroadcastStart() {
    IStreamAwareScopeHandler handler = getStreamAwareHandler();
    if (handler != null) {
      try {
        handler.streamBroadcastStart(this);
      } catch (Throwable t) {
        log.error("error notify streamBroadcastStart", t);
      }
    }
  }

  /**
   * Pull the next message from IMessageInput and schedule it for push according to the timestamp.
   */
  protected void scheduleNextMessage() {
    boolean first = (nextRTMPMessage == null);
    long delta = 0L;
    do {
      nextRTMPMessage = getNextRTMPMessage();
      if (nextRTMPMessage != null) {
        IRTMPEvent rtmpEvent = nextRTMPMessage.getBody();
        // filter all non-AV messages
        if (rtmpEvent instanceof VideoData || rtmpEvent instanceof AudioData) {
          rtmpEvent = nextRTMPMessage.getBody();
          nextTS = rtmpEvent.getTimestamp();
          if (first) {
            vodStartTS = nextTS;
            first = false;
          }
          delta = nextTS - vodStartTS - (System.currentTimeMillis() - serverStartTS);
          if (delta < WAIT_THRESHOLD) {
            if (doPushMessage()) {
              if (state != StreamState.PLAYING) {
                // Stream is not playing, don't load more messages
                nextRTMPMessage = null;
              }
            } else {
              nextRTMPMessage = null;
            }
          }     
        }
      } else {
        onItemEnd();
      }
    } while (nextRTMPMessage != null || delta < WAIT_THRESHOLD);
    // start the job all over again
    vodJobName = scheduler.addScheduledOnceJob(delta, new IScheduledJob() {
      public void execute(ISchedulingService service) {
        if (vodJobName != null) {
          vodJobName = null;
          if (doPushMessage()) {
            if (state == StreamState.PLAYING) {
              scheduleNextMessage();
            } else {
              // Stream is paused, don't load more messages
              nextRTMPMessage = null;
            }
          }
        }
      }
    });
  }

  private boolean doPushMessage() {
    boolean sent = false;
    long start = currentItem.getStart();
    if (start < 0) {
      start = 0;
    }
    if (currentItem.getLength() >= 0 && nextTS - start > currentItem.getLength()) {
      onItemEnd();
      return sent;
    }
    if (nextRTMPMessage != null) {
      sent = true;
      try {
        pushMessage(nextRTMPMessage);
      } catch (IOException err) {
        log.error("Error while sending message.", err);
      }
      nextRTMPMessage.getBody().release();
    }
    return sent;
  }

  /**
   * Getter for next RTMP message.
   *
   * @return  Next RTMP message
   */
  protected RTMPMessage getNextRTMPMessage() {
    IMessage message;
    do {
      // Pull message from message input object...
      try {
        message = msgIn.pullMessage();
      } catch (Exception err) {
        log.error("Error while pulling message.", err);
        message = null;
      }
      // If message is null then return null
      if (message == null) {
        return null;
      }
    } while (!(message instanceof RTMPMessage));
    // Cast and return
    return (RTMPMessage) message;
  }

  /**
   * Send VOD initialization control message
   * @param msgIn            Message input
   * @param start            Start timestamp
   */
  private void sendVODInitCM(IMessageInput msgIn, int start) {
    if (msgIn != null) {
      // Create new out-of-band control message
      OOBControlMessage oobCtrlMsg = new OOBControlMessage();
      // Set passive type
      oobCtrlMsg.setTarget(IPassive.KEY);
      // Set service name of init
      oobCtrlMsg.setServiceName("init");
      // Create map for parameters
      Map<String, Object> paramMap = new HashMap<String, Object>(1);
      // Put start timestamp into Map of params
      paramMap.put("startTS", start);
      // Attach to OOB control message and send it
      oobCtrlMsg.setServiceParamMap(paramMap);
      msgIn.sendOOBControlMessage(this, oobCtrlMsg);
    }
  }

  /**
   * Send VOD seek control message
   *
   * @param msgIn        Message input
   * @param position      New timestamp to play from
   */
  private void sendVODSeekCM(IMessageInput msgIn, int position) {
    OOBControlMessage oobCtrlMsg = new OOBControlMessage();
    oobCtrlMsg.setTarget(ISeekableProvider.KEY);
    oobCtrlMsg.setServiceName("seek");
    Map<String, Object> paramMap = new HashMap<String, Object>(1);
    paramMap.put("position", Integer.valueOf(position));
    oobCtrlMsg.setServiceParamMap(paramMap);
    msgIn.sendOOBControlMessage(this, oobCtrlMsg);
    // Reset properties
    vodStartTS = 0;
    serverStartTS = System.currentTimeMillis();
    if (nextRTMPMessage != null) {
      try {
        pushMessage(nextRTMPMessage);
      } catch (IOException err) {
        log.error("Error while sending message.", err);
      }
      nextRTMPMessage.getBody().release();
      nextRTMPMessage = null;
    }
    ResetMessage reset = new ResetMessage();
    try {
      pushMessage(reset);
    } catch (IOException err) {
      log.error("Error while sending message.", err);
    }
    scheduleNextMessage();
  }

  /**
   * Move to the next item updating the currentItemIndex.
   */
  protected void moveToNext() {
    if (currentItemIndex >= items.size()) {
      currentItemIndex = items.size() - 1;
    }
    if (controller != null) {
      currentItemIndex = controller.nextItem(this, currentItemIndex);
    } else {
      currentItemIndex = defaultController.nextItem(this, currentItemIndex);
    }
  }

  /**
   * Move to the previous item updating the currentItemIndex.
   */
  protected void moveToPrevious() {
    if (currentItemIndex >= items.size()) {
      currentItemIndex = items.size() - 1;
    }
    if (controller != null) {
      currentItemIndex = controller.previousItem(this, currentItemIndex);
    } else {
      currentItemIndex = defaultController.previousItem(this, currentItemIndex);
    }
  }

  public void addStreamListener(IStreamListener listener) {
    listeners.add(listener);
  }

  public Collection<IStreamListener> getStreamListeners() {
    return listeners;
  }

  public void removeStreamListener(IStreamListener listener) {
    listeners.remove(listener);
  }

  /* (non-Javadoc)
   * @see java.lang.Object#toString()
   */
  @Override
  public String toString() {
    return "ServerStream [publishedName=" + publishedName + ", controller=" + controller + ", defaultController=" + defaultController + ", isRewind=" + isRewind
        + ", isRandom=" + isRandom + ", isRepeat=" + isRepeat + ", items=" + items + ", currentItemIndex=" + currentItemIndex + ", currentItem=" + currentItem
        + ", providerService=" + providerService + ", scheduler=" + scheduler + ", liveJobName=" + liveJobName
        + ", vodJobName=" + vodJobName + ", vodStartTS=" + vodStartTS + ", serverStartTS=" + serverStartTS + ", nextTS=" + nextTS + "]";
  }

}
TOP

Related Classes of org.red5.server.stream.ServerStream

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.