summaryrefslogtreecommitdiff
path: root/activity_monitor.c
blob: 53e3d7f1a2b4b725cf78e9aa6dbd0ea250916911 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
/*
 * Activity Monitor
 * 
 * Contains code to run an activity flag and associated timer
 * A pthread implements a simple state machine with three states, 
 * "idle", "active" and "timing out".
 * 
 * 
 * This file is part of Shairport Sync.
 * Copyright (c) Mike Brady 2019
 * All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

#include <stdlib.h>
#include <errno.h>
#include <inttypes.h>
#include <sys/types.h>

#include "config.h"

#include "common.h"
#include "rtsp.h"
#include "activity_monitor.h"

enum am_state {am_inactive, am_active, am_timing_out} state;
enum ps_state {ps_inactive, ps_active} player_state;

pthread_t activity_monitor_thread;
pthread_mutex_t activity_monitor_mutex;
pthread_cond_t activity_monitor_cv;

void going_active(int block) {
  // debug(1, "activity_monitor: state transitioning to \"active\" with%s blocking", block ? "" : "out");
  if (config.cmd_active_start)
    command_execute(config.cmd_active_start, "", block);
#ifdef CONFIG_METADATA
    debug(2, "abeg"); // active mode begin
    send_ssnc_metadata('pend', NULL, 0, 1); // contains cancellation points
#endif
}

void going_inactive(int block) {
   // debug(1, "activity_monitor: state transitioning to \"inactive\" with%s blocking", block ? "" : "out");
  if (config.cmd_active_stop)
    command_execute(config.cmd_active_stop, "", block);
#ifdef CONFIG_METADATA
    debug(2, "aend"); // active mode end
    send_ssnc_metadata('pend', NULL, 0, 1); // contains cancellation points
#endif
}

void activity_monitor_signify_activity(int active) {
  pthread_mutex_lock(&activity_monitor_mutex);
  player_state = active == 0 ? ps_inactive : ps_active;
  // Now, although we could simply let the state machine in the activity monitor thread
  // look after eveything, we will change state here in two situations:
  // 1. If the state machine is am_inactive and the player is ps_active
  // we will change the state to am_active and execute the going_active() function.
  // 2. If the state machine is am_active and the player is ps_inactive and
  // the activity_idle_timeout is 0, then we will change the state to am_inactive and
  // execute the going_inactive() function.
  //
  // The reason for all this is that we might want to perform the attached scripts
  // and wait for them to complete before continuing. If they were perfomed in the
  // activity monitor thread, then we coldn't wait for them to complete.
  
  // Thus, the only time the thread will execute a going_... function is when a non-zero
  // timeout actually matures.
  
  if ((state == am_inactive) && (player_state == ps_active)) {
    going_active(config.cmd_blocking); // note -- will be executed with the mutex locked, but that's okay
  } else if ((state == am_active) && (player_state == ps_inactive) && (config.active_mode_timeout == 0.0)) {
    going_inactive(config.cmd_blocking); // note -- will be executed with the mutex locked, but that's okay
  }
  
  pthread_cond_signal(&activity_monitor_cv); 
  pthread_mutex_unlock(&activity_monitor_mutex);
}

void activity_thread_cleanup_handler(__attribute__((unused)) void *arg) {
  debug(3, "activity_monitor: thread exit.");
  pthread_cond_destroy(&activity_monitor_cv);
  pthread_mutex_destroy(&activity_monitor_mutex);
}

void *activity_monitor_thread_code(void *arg) {
  int rc = pthread_mutex_init(&activity_monitor_mutex, NULL);
  if (rc)
    die("activity_monitor: error %d initialising activity_monitor_mutex.", rc);

// set the flowcontrol condition variable to wait on a monotonic clock
#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD
  pthread_condattr_t attr;
  pthread_condattr_init(&attr);
  pthread_condattr_setclock(&attr, CLOCK_MONOTONIC); // can't do this in OS X, and don't need it.
  rc = pthread_cond_init(&activity_monitor_cv, &attr);
  pthread_condattr_destroy(&attr);
  
#endif
#ifdef COMPILE_FOR_OSX
  rc = pthread_cond_init(&activity_monitor_cv, NULL);
#endif
  if (rc)
    die("activity_monitor: error %d initialising activity_monitor_cv.");
  pthread_cleanup_push(activity_thread_cleanup_handler, arg);
  
  uint64_t sec;
  uint64_t nsec;
  int rc;
  struct timespec time_for_wait;
  
  state = am_inactive;
  player_state = ps_inactive;
  
  pthread_mutex_lock(&activity_monitor_mutex);
  do {
    switch (state) {
      case am_inactive:
       // debug(1,"am_state: am_inactive");
       while (player_state != ps_active)
          pthread_cond_wait(&activity_monitor_cv, &activity_monitor_mutex);               
        state = am_active;
        // going_active(); // this is done in activity_monitor_signify_activity         
      break;
      case am_active:
       // debug(1,"am_state: am_active");
        while (player_state != ps_inactive)
          pthread_cond_wait(&activity_monitor_cv, &activity_monitor_mutex);
        if (config.active_mode_timeout == 0.0) {
          state = am_inactive;
          // going_inactive(); // this is done in activity_monitor_signify_activity
        } else {
          state = am_timing_out;

          uint64_t time_to_wait_for_wakeup_fp = (uint64_t)(config.active_mode_timeout *1000000); // resolution of microseconds
          time_to_wait_for_wakeup_fp = time_to_wait_for_wakeup_fp << 32;
          time_to_wait_for_wakeup_fp = time_to_wait_for_wakeup_fp / 1000000;
          
#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD
          uint64_t time_of_wakeup_fp = get_absolute_time_in_fp() + time_to_wait_for_wakeup_fp;
          sec = time_of_wakeup_fp >> 32;
          nsec = ((time_of_wakeup_fp & 0xffffffff) * 1000000000) >> 32;
          time_for_wait.tv_sec = sec;
          time_for_wait.tv_nsec = nsec;
#endif
#ifdef COMPILE_FOR_OSX
          sec = time_to_wait_for_wakeup_fp >> 32;
          nsec = ((time_to_wait_for_wakeup_fp & 0xffffffff) * 1000000000) >> 32;
          time_for_wait.tv_sec = sec;
          time_for_wait.tv_nsec = nsec;
#endif
        }      
      break;
      case am_timing_out:
       // debug(1,"am_state: am_timing_out");
        rc = 0;
        while ((player_state != ps_active) && (rc != ETIMEDOUT)) {
#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD
          rc = pthread_cond_timedwait(&activity_monitor_cv, &activity_monitor_mutex, &time_for_wait); // this is a pthread cancellation point
#endif
#ifdef COMPILE_FOR_OSX
          rc = pthread_cond_timedwait_relative_np(&activity_monitor_cv, &activity_monitor_mutex, &time_for_wait);         
#endif
        }
        if (player_state == ps_active)
          state = am_active; // player has gone active -- do nothing, because it's still active
        else if (rc == ETIMEDOUT) {
            state = am_inactive;
            pthread_mutex_unlock(&activity_monitor_mutex);
            going_inactive(0); // don't wait for completion -- it makes no sense   
            pthread_mutex_lock(&activity_monitor_mutex);
        }  else {
            // activity monitor was woken up in the state am_timing_out, but not by a timeout and player is not in ps_active state
            debug(1, "activity monitor was woken up in the state am_timing_out, but didn't change state");
        }        
      break;
      default:
        debug(1,"activity monitor in an illegal state!");
        state = am_inactive;
      break;
    }
  } while (1);
  pthread_mutex_unlock(&activity_monitor_mutex);  
  pthread_cleanup_pop(0); // should never happen
  pthread_exit(NULL);
}

void activity_monitor_start() {
  // debug(1,"activity_monitor_start");
  pthread_create(&activity_monitor_thread, NULL, activity_monitor_thread_code, NULL);
}

void activity_monitor_stop() {
  debug(1,"activity_monitor_stop start...");
  pthread_cancel(activity_monitor_thread);
  pthread_join(activity_monitor_thread,NULL);
  debug(1,"activity_monitor_stop complete");
}