📄 handler.java
字号:
{ logger.log(Level.WARNING, "" + e, e); postControllerErrorEvent("" + e); return false; } // now that nodes are open, add controls: if (mode == PLAYER && audioTrackIndex >= 0) // if it has a audio track { final RendererNode rendererNode = (RendererNode) FilterGraph.getTail(root.getDestLink(audioTrackIndex).getDestNode()); if (rendererNode != null) { final Renderer renderer = rendererNode.getRenderer(); // add any controls from renderer GainControl gainControl = (GainControl) renderer.getControl(GainControl.class.getName()); if (gainControl != null) setGainControl(gainControl); } } // TODO: when to call open and when to call start? if (TRACE) logger.fine("Starting filter graph(s)"); try { FilterGraph.start(root); } catch (IOException e) { logger.log(Level.WARNING, "" + e, e); postControllerErrorEvent("" + e); return false; } // TODO: the track threads need to coordinate better. Perhaps each track thread // should really be its own controller, and this controller is a multi-controller (See EJMF example). trackThreads = new TrackThread[root.getNumDestLinks()]; for (int i = 0; i < root.getNumDestLinks(); ++i) { if (root.getDestLink(i) != null) { final DemuxNode rootCopy = (DemuxNode) root.duplicate(); trackThreads[i] = new TrackThread(rootCopy, i, videoTrackIndex == i ? FilterGraph.SUPPRESS_TRACK_READ : FilterGraph.PROCESS_DEFAULT); // use a copy of the graph for each thread, so that they don't interfere with each other. if (videoTrackIndex == i) // if it has a video track - read the first video frame { FilterGraph.process(rootCopy, null, videoTrackIndex, -1, FilterGraph.PROCESS_DEFAULT); // doesn't work because we have to use the actual filter graph clone that is used for the track. } } } // if (videoTrackIndex >= 0) // if it has a video track - read the first video frame// { FilterGraph.process(root, null, videoTrackIndex, FilterGraph.PROCESS_DEFAULT); // doesn't work because we have to use the actual filter graph clone that is used for the track.// }// // TODO: we need the first frame (video only) to size the window right. //readAndProcessNextFrame(); // we have to wait for the component to be visible to start really playing, and do this in another thread return true; } public Component getControlPanelComponent() { Component c = super.getControlPanelComponent(); if( c == null ) { c = new StandardControlPanel(this, StandardControlPanel.USE_START_CONTROL | StandardControlPanel.USE_STOP_CONTROL | StandardControlPanel.USE_PROGRESS_CONTROL); setControlPanelComponent(c); } return c; } private class TrackThread extends CloseableThread { private final int trackNumber; private final DemuxNode root; private final int firstFlags; private long nanoseconds; // TODO: set this properly private boolean eom = false; public TrackThread(DemuxNode root, int trackNumber, int firstFlags) { super(); setName("TrackThread for track " + trackNumber); this.root = root; this.trackNumber = trackNumber; this.firstFlags = firstFlags; } @Override public void close() { // don't call super.close(), which interrupts. // it seems risky to be interrupting the thread, which could be in any // possible state. We would basically lose frames this way. // so the downside of not interrupting is that it may take longer than // expected to fully stop. // Interrupting would leave the media in an ungraceful state. // we may have buffers that are read, that will never be rendered, etc. // we may want to find a way to do this without interruption. // TODO: what we really want to do is keep any buffers around, and simply // continue processing them if we resume. // really, we want to basically suspend the thread and resume it. setClosing(); } // returns false on EOM. private Buffer readAndProcessNextFrame(int flags) {// TODO: we need to honor isEnabled when processing tracks FilterGraph.process(root, null, trackNumber, -1, flags); final Buffer b = (Buffer) root.getOutputBuffer(trackNumber); if (b == null) throw new NullPointerException(); return b; } public void run() { try { final float rate = getRate(); // what if this changes during playback? It won't because we'll get stopped and started in that case. try { final FilterGraphNode penultimateNode = FilterGraph.getBeforeTail(root.getDestLink(trackNumber).getDestNode()); boolean first = true; boolean inputEOM = false; // set to true when the input buffer reaches EOM, which may be before the last buffer in the chain reaches EOM. while (true) { if (isClosing()) return; final int flags; if (first) flags = firstFlags; // if firstFlags == FilterGraph.SUPPRESS_TRACK_READ, first frame has already been read, only needs redisplay else if (inputEOM) flags = FilterGraph.SUPPRESS_TRACK_READ; else flags = FilterGraph.PROCESS_DEFAULT; Buffer b = readAndProcessNextFrame(flags); if (isClosing()) return; if (first) { first = false; } if (b.isEOM()) inputEOM = true; // we continue processing, however, until the final buffer in the chain gets EOM. // this is needed to handle the case where there is residual data in the codecs. //TODO: how close is this to what JMF does? How does JMF handle this? // if we don't do this, the effect is that codecs that are not 1:1 in terms of buffers will have any // data buffered up in the codecs cut off. if (b.isDiscard()) continue; if (!(penultimateNode instanceof RendererNode) && !(penultimateNode instanceof MuxNode)) { // we need to sleep based on the time in the processed data, not the input data. // if the graph goes demux->renderer, then the output buffer of the demux, // which is set to b above, is what we use to check. b = penultimateNode.getOutputBuffer(0); // TODO: this can cause a NPE below if b is null (not sure how it could be null) if (b == null) continue; // this can be null if it has not been set yet, if codecs earlier in the chain have been // returning INPUT_BUFFER_NOT_CONSUMED or OUTPUT_BUFFER_NOT_FILLED. } if (b.isEOM()) // TODO: is there data in an eom buffer? break; if (b.isDiscard()) continue; // Update the video time nanoseconds = b.getTimeStamp(); } eom = true; checkAllTracksEOM(); } catch (Exception e) { logger.log(Level.WARNING, "" + e, e); postControllerErrorEvent("" + e); } } finally { setClosed(); } } public boolean isEOM() { return eom; } } /** * If all tracks have reached eom, then we post an end of media event. * */ private void checkAllTracksEOM() { // TODO: synchronize on something? for (int i = 0; i < trackThreads.length; ++i) { if (!trackThreads[i].isEOM()) return; } // TODO: not once for each track try { endOfMedia(); } catch(ClockStoppedException e) { postEvent( new InternalErrorEvent(Handler.this, "Controller not in Started state at EOM") ); } } //@Override public void doPlayerSetMediaTime(Time t) { logger.info("Handler.doPlayerSetMediaTime" + t); // TODO: bounds checking root.getDemux().setPosition(t, 0); // TODO: rounding? // TODO: // long nanoseconds = t.getNanoseconds();// // // Enforce bounds// if( nanoseconds > duration.getNanoseconds() ) {// nanoseconds = duration.getNanoseconds();// } else//// if( nanoseconds < 0 ) {// nanoseconds = 0;// }//// // If the video is currently playing and hasn't reached// // the end, then stop it, reset the nanosecond offset,// // and restart it. Otherwise, just set the nanosecond// // offset.//// if( playthread != null &&// playthread.isAlive() &&// ! endOfMedia() )// {// stop();// this.nanoseconds = nanoseconds;// start();// } else {// this.nanoseconds = nanoseconds;// }// calcInitFrame(); } //@Override public float doPlayerSetRate(float rate) { logger.info("Handler.doPlayerSetRate " + rate); // Zero is the only invalid rate if( rate == 0 ) return getRate(); // TODO: neg. not supported if( rate < 0 ) return getRate(); // TODO: // // If the video is currently playing and hasn't reached// // the end, then stop it, reset the rate, and restart it.// // Otherwise, just set the nanosecond offset.//// if( playthread != null &&// playthread.isAlive() &&// ! endOfMedia() )// {// stop();// this.rate = rate;// start();// } else {// this.rate = rate;// } //calcInitFrame(); return rate; } //@Override public boolean doPlayerStop() { logger.info("Handler.doPlayerStop"); if (trackThreads == null) return true; final List<TrackThread> waitUntilClosed = new ArrayList<TrackThread>(); for (int i = 0; i < trackThreads.length; ++i) { final TrackThread t = trackThreads[i]; if (t != null && t.isAlive()) { t.close(); // shut down gracefully waitUntilClosed.add(t); } } // // TODO: is it legal to call stop (for example Renderer.stop), potentially while // // Renderer.process is being called? This seems like this could cause// // more problems than it solves. Perhaps we should wait for the tracks// // threads to complete before doing this. This requires that the// // nodes themselves will successfully respond to an interruption.// try// {// FilterGraph.stop(root);// } catch (IOException e)// {// logger.log(Level.WARNING, "" + e, e);// postControllerErrorEvent("" + e);// return false;// } for (TrackThread t : waitUntilClosed) { // make sure threads are really stopped. this can cause a thread deadlock. try { t.waitUntilClosed(); } catch (InterruptedException e) { logger.log(Level.WARNING, "" + e, e); return false; } } // stop after the threads have completed, avoiding any threadin problems with // process on the filter graph. try
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -