]> git.ipfire.org Git - thirdparty/shairport-sync.git/blame - activity_monitor.c
Update check_classic_mac_basic.yml
[thirdparty/shairport-sync.git] / activity_monitor.c
CommitLineData
a8639f6c
MB
1/*
2 * Activity Monitor
2e442853 3 *
a8639f6c 4 * Contains code to run an activity flag and associated timer
2e442853 5 * A pthread implements a simple state machine with three states,
a8639f6c 6 * "idle", "active" and "timing out".
2e442853
MB
7 *
8 *
a8639f6c
MB
9 * This file is part of Shairport Sync.
10 * Copyright (c) Mike Brady 2019
11 * All rights reserved.
12 *
13 * Permission is hereby granted, free of charge, to any person
14 * obtaining a copy of this software and associated documentation
15 * files (the "Software"), to deal in the Software without
16 * restriction, including without limitation the rights to use,
17 * copy, modify, merge, publish, distribute, sublicense, and/or
18 * sell copies of the Software, and to permit persons to whom the
19 * Software is furnished to do so, subject to the following conditions:
20 *
21 * The above copyright notice and this permission notice shall be
22 * included in all copies or substantial portions of the Software.
23 *
24 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
26 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
27 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
28 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
29 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
30 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
31 * OTHER DEALINGS IN THE SOFTWARE.
32 */
33
a8639f6c
MB
34#include <errno.h>
35#include <inttypes.h>
2e442853 36#include <stdlib.h>
a8639f6c
MB
37#include <sys/types.h>
38
39#include "config.h"
40
2e442853 41#include "activity_monitor.h"
a8639f6c 42#include "common.h"
604ca8de 43#include "rtsp.h"
a8639f6c 44
58acd161
MB
45#ifdef CONFIG_DBUS_INTERFACE
46#include "dbus-service.h"
47#endif
48
c86823c3 49enum am_state state;
2e442853 50enum ps_state { ps_inactive, ps_active } player_state;
a8639f6c 51
79e22063
MB
52int activity_monitor_running = 0;
53
a8639f6c
MB
54pthread_t activity_monitor_thread;
55pthread_mutex_t activity_monitor_mutex;
56pthread_cond_t activity_monitor_cv;
57
2e181f93 58void going_active(int block) {
e8b8c6ed
MB
59 // debug(1, "activity_monitor: state transitioning to \"active\" with%s blocking", block ? "" :
60 // "out");
2e181f93
MB
61 if (config.cmd_active_start)
62 command_execute(config.cmd_active_start, "", block);
604ca8de 63#ifdef CONFIG_METADATA
2e442853 64 debug(2, "abeg"); // active mode begin
70f60fcd 65 send_ssnc_metadata('abeg', NULL, 0, 1); // contains cancellation points
604ca8de 66#endif
34351a99
MB
67
68#ifdef CONFIG_DBUS_INTERFACE
97a4766e 69 if (dbus_service_is_running())
34351a99
MB
70 shairport_sync_set_active(SHAIRPORT_SYNC(shairportSyncSkeleton), TRUE);
71#endif
72
83c0405d 73 if (config.disable_standby_mode == disable_standby_auto) {
6b6d5986 74 config.keep_dac_busy = 1;
34351a99 75 }
a8639f6c
MB
76}
77
2e181f93 78void going_inactive(int block) {
e8b8c6ed
MB
79 // debug(1, "activity_monitor: state transitioning to \"inactive\" with%s blocking", block ? "" :
80 // "out");
2e181f93
MB
81 if (config.cmd_active_stop)
82 command_execute(config.cmd_active_stop, "", block);
604ca8de 83#ifdef CONFIG_METADATA
2e442853 84 debug(2, "aend"); // active mode end
70f60fcd 85 send_ssnc_metadata('aend', NULL, 0, 1); // contains cancellation points
604ca8de 86#endif
34351a99
MB
87
88#ifdef CONFIG_DBUS_INTERFACE
97a4766e 89 if (dbus_service_is_running())
34351a99
MB
90 shairport_sync_set_active(SHAIRPORT_SYNC(shairportSyncSkeleton), FALSE);
91#endif
92
83c0405d 93 if (config.disable_standby_mode == disable_standby_auto) {
a382ac66 94 config.keep_dac_busy = 0;
34351a99 95 }
a8639f6c
MB
96}
97
2e181f93 98void activity_monitor_signify_activity(int active) {
d8bad42b 99 // this could be pthread_cancelled and there is likely to be cancellation points in the
20967812 100 // hooked-on procedures
4d608216 101 pthread_mutex_lock(&activity_monitor_mutex);
2e181f93
MB
102 player_state = active == 0 ? ps_inactive : ps_active;
103 // Now, although we could simply let the state machine in the activity monitor thread
4d608216 104 // look after everything, we will change state here in two situations:
2e181f93
MB
105 // 1. If the state machine is am_inactive and the player is ps_active
106 // we will change the state to am_active and execute the going_active() function.
107 // 2. If the state machine is am_active and the player is ps_inactive and
108 // the activity_idle_timeout is 0, then we will change the state to am_inactive and
109 // execute the going_inactive() function.
110 //
111 // The reason for all this is that we might want to perform the attached scripts
b9cf91b2 112 // and wait for them to complete before continuing. If they were performed in the
20967812 113 // activity monitor thread, then we couldn't wait for them to complete.
2e442853 114
4d608216
MB
115 // So, if the active end procedure is on a timer, it will be executed when the
116 // timeout occurs and the "blocking" status is ignored.
fd880056 117
2e181f93 118 if ((state == am_inactive) && (player_state == ps_active)) {
4d608216
MB
119 state = am_active;
120 pthread_mutex_unlock(&activity_monitor_mutex);
fd880056 121 going_active(config.cmd_blocking);
2e442853 122 } else if ((state == am_active) && (player_state == ps_inactive) &&
a9000de6 123 (config.active_state_timeout == 0.0)) {
4d608216
MB
124 state = am_inactive;
125 pthread_mutex_unlock(&activity_monitor_mutex);
fd880056 126 going_inactive(config.cmd_blocking);
4d608216
MB
127 } else {
128 pthread_mutex_unlock(&activity_monitor_mutex);
2e181f93 129 }
4d608216
MB
130 // lock the mutex again to send a signal
131 pthread_cleanup_debug_mutex_lock(&activity_monitor_mutex, 10000, 1);
2e442853 132 pthread_cond_signal(&activity_monitor_cv);
20967812 133 pthread_cleanup_pop(1); // release the mutex
a8639f6c
MB
134}
135
136void activity_thread_cleanup_handler(__attribute__((unused)) void *arg) {
137 debug(3, "activity_monitor: thread exit.");
138 pthread_cond_destroy(&activity_monitor_cv);
139 pthread_mutex_destroy(&activity_monitor_mutex);
140}
141
142void *activity_monitor_thread_code(void *arg) {
143 int rc = pthread_mutex_init(&activity_monitor_mutex, NULL);
144 if (rc)
145 die("activity_monitor: error %d initialising activity_monitor_mutex.", rc);
146
a8639f6c 147 rc = pthread_cond_init(&activity_monitor_cv, NULL);
a8639f6c
MB
148 if (rc)
149 die("activity_monitor: error %d initialising activity_monitor_cv.");
150 pthread_cleanup_push(activity_thread_cleanup_handler, arg);
2e442853 151
a8639f6c
MB
152 uint64_t sec;
153 uint64_t nsec;
a8639f6c 154 struct timespec time_for_wait;
2e442853 155
a8639f6c
MB
156 state = am_inactive;
157 player_state = ps_inactive;
2e442853 158
a8639f6c
MB
159 pthread_mutex_lock(&activity_monitor_mutex);
160 do {
161 switch (state) {
2e442853 162 case am_inactive:
c0a3dacf 163 debug(2, "am_state: am_inactive");
2e442853
MB
164 while (player_state != ps_active)
165 pthread_cond_wait(&activity_monitor_cv, &activity_monitor_mutex);
4d608216
MB
166 // state = am_active; this is done by the activity_monitor_signify_activity(1) function
167 debug(2, "am_state: am_active");
a8639f6c 168 break;
2e442853
MB
169 case am_active:
170 // debug(1,"am_state: am_active");
171 while (player_state != ps_inactive)
172 pthread_cond_wait(&activity_monitor_cv, &activity_monitor_mutex);
fd880056 173
4d608216
MB
174 // if it's not already am_inactive, the it should be beginning to time out...
175 if (state != am_inactive) {
2e442853
MB
176 state = am_timing_out;
177
67e9b1b6 178 uint64_t time_to_wait_for_wakeup_ns = (uint64_t)(config.active_state_timeout * 1000000000);
2e442853 179
a8639f6c 180#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD
56bef8e7 181 uint64_t time_of_wakeup_ns = get_realtime_in_ns() + time_to_wait_for_wakeup_ns;
67e9b1b6
MB
182 sec = time_of_wakeup_ns / 1000000000;
183 nsec = time_of_wakeup_ns % 1000000000;
2e442853
MB
184 time_for_wait.tv_sec = sec;
185 time_for_wait.tv_nsec = nsec;
a8639f6c 186#endif
67e9b1b6 187
a8639f6c 188#ifdef COMPILE_FOR_OSX
67e9b1b6
MB
189 sec = time_to_wait_for_wakeup_ns / 1000000000;
190 nsec = time_to_wait_for_wakeup_ns % 1000000000;
2e442853
MB
191 time_for_wait.tv_sec = sec;
192 time_for_wait.tv_nsec = nsec;
a8639f6c 193#endif
2e442853 194 }
a8639f6c 195 break;
2e442853 196 case am_timing_out:
2e442853
MB
197 rc = 0;
198 while ((player_state != ps_active) && (rc != ETIMEDOUT)) {
a8639f6c 199#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD
2e442853
MB
200 rc = pthread_cond_timedwait(&activity_monitor_cv, &activity_monitor_mutex,
201 &time_for_wait); // this is a pthread cancellation point
a8639f6c
MB
202#endif
203#ifdef COMPILE_FOR_OSX
2e442853
MB
204 rc = pthread_cond_timedwait_relative_np(&activity_monitor_cv, &activity_monitor_mutex,
205 &time_for_wait);
a8639f6c 206#endif
2e442853
MB
207 }
208 if (player_state == ps_active)
209 state = am_active; // player has gone active -- do nothing, because it's still active
210 else if (rc == ETIMEDOUT) {
a8639f6c 211 state = am_inactive;
2e442853
MB
212 pthread_mutex_unlock(&activity_monitor_mutex);
213 going_inactive(0); // don't wait for completion -- it makes no sense
214 pthread_mutex_lock(&activity_monitor_mutex);
215 } else {
216 // activity monitor was woken up in the state am_timing_out, but not by a timeout and player
217 // is not in ps_active state
218 debug(1,
219 "activity monitor was woken up in the state am_timing_out, but didn't change state");
220 }
221 break;
222 default:
223 debug(1, "activity monitor in an illegal state!");
224 state = am_inactive;
a8639f6c
MB
225 break;
226 }
227 } while (1);
2e442853 228 pthread_mutex_unlock(&activity_monitor_mutex);
a8639f6c
MB
229 pthread_cleanup_pop(0); // should never happen
230 pthread_exit(NULL);
231}
232
c8b0be30 233enum am_state activity_status() { return (state); }
c86823c3 234
a8639f6c 235void activity_monitor_start() {
604ca8de 236 // debug(1,"activity_monitor_start");
a8639f6c 237 pthread_create(&activity_monitor_thread, NULL, activity_monitor_thread_code, NULL);
79e22063 238 activity_monitor_running = 1;
a8639f6c
MB
239}
240
241void activity_monitor_stop() {
79e22063 242 if (activity_monitor_running) {
a9000de6
MB
243 debug(2, "activity_monitor_stop begin. state: %d, player_state: %d.", state, player_state);
244 if ((state == am_active) || (state == am_timing_out)) {
245 going_inactive(config.cmd_blocking);
246 state = am_inactive;
247 }
79e22063
MB
248 pthread_cancel(activity_monitor_thread);
249 pthread_join(activity_monitor_thread, NULL);
405a028f 250 debug(2, "activity_monitor_stop complete");
79e22063 251 }
a8639f6c 252}