001// Copyright (c) FIRST and other WPILib contributors. 002// Open Source Software; you can modify and/or share it under the terms of 003// the WPILib BSD license file in the root directory of this project. 004 005package edu.wpi.first.cscore; 006 007import java.util.HashMap; 008import java.util.Map; 009import java.util.concurrent.locks.Condition; 010import java.util.concurrent.locks.ReentrantLock; 011import java.util.function.Consumer; 012 013/** 014 * An event listener. This calls back to a designated callback function when an event matching the 015 * specified mask is generated by the library. 016 */ 017public class VideoListener implements AutoCloseable { 018 /** 019 * Create an event listener. 020 * 021 * @param listener Listener function 022 * @param eventMask Bitmask of VideoEvent.Type values 023 * @param immediateNotify Whether callback should be immediately called with a representative set 024 * of events for the current library state. 025 */ 026 public VideoListener(Consumer<VideoEvent> listener, int eventMask, boolean immediateNotify) { 027 s_lock.lock(); 028 try { 029 if (s_poller == 0) { 030 s_poller = CameraServerJNI.createListenerPoller(); 031 startThread(); 032 } 033 m_handle = CameraServerJNI.addPolledListener(s_poller, eventMask, immediateNotify); 034 s_listeners.put(m_handle, listener); 035 } finally { 036 s_lock.unlock(); 037 } 038 } 039 040 @Override 041 public synchronized void close() { 042 if (m_handle != 0) { 043 s_lock.lock(); 044 try { 045 s_listeners.remove(m_handle); 046 } finally { 047 s_lock.unlock(); 048 } 049 CameraServerJNI.removeListener(m_handle); 050 m_handle = 0; 051 } 052 } 053 054 /** 055 * Returns true if the video listener handle is valid. 056 * 057 * @return True if the video listener handle is valid. 058 */ 059 public boolean isValid() { 060 return m_handle != 0; 061 } 062 063 private int m_handle; 064 065 private static final ReentrantLock s_lock = new ReentrantLock(); 066 private static final Map<Integer, Consumer<VideoEvent>> s_listeners = new HashMap<>(); 067 068 @SuppressWarnings("PMD.SingularField") 069 private static Thread s_thread; 070 071 private static int s_poller; 072 private static boolean s_waitQueue; 073 private static final Condition s_waitQueueCond = s_lock.newCondition(); 074 075 private static void startThread() { 076 s_thread = 077 new Thread( 078 () -> { 079 boolean wasInterrupted = false; 080 while (!Thread.interrupted()) { 081 VideoEvent[] events; 082 try { 083 events = CameraServerJNI.pollListener(s_poller); 084 } catch (InterruptedException ex) { 085 s_lock.lock(); 086 try { 087 if (s_waitQueue) { 088 s_waitQueue = false; 089 s_waitQueueCond.signalAll(); 090 continue; 091 } 092 } finally { 093 s_lock.unlock(); 094 } 095 Thread.currentThread().interrupt(); 096 // don't try to destroy poller, as its handle is likely no longer valid 097 wasInterrupted = true; 098 break; 099 } 100 for (VideoEvent event : events) { 101 Consumer<VideoEvent> listener; 102 s_lock.lock(); 103 try { 104 listener = s_listeners.get(event.listener); 105 } finally { 106 s_lock.unlock(); 107 } 108 if (listener != null) { 109 try { 110 listener.accept(event); 111 } catch (Throwable throwable) { 112 System.err.println( 113 "Unhandled exception during listener callback: " + throwable); 114 throwable.printStackTrace(); 115 } 116 } 117 } 118 } 119 s_lock.lock(); 120 try { 121 if (!wasInterrupted) { 122 CameraServerJNI.destroyListenerPoller(s_poller); 123 } 124 s_poller = 0; 125 } finally { 126 s_lock.unlock(); 127 } 128 }, 129 "VideoListener"); 130 s_thread.setDaemon(true); 131 s_thread.start(); 132 } 133}