static void * spawn_pipe_thread(void *aux) { tvhpoll_event_t ev[2]; tvhpoll_t *efd = tvhpoll_create(2); int nfds; memset(ev, 0, sizeof(ev)); ev[0].events = TVHPOLL_IN; ev[0].fd = spawn_pipe_info.rd; ev[0].data.ptr = &spawn_pipe_info; ev[1].events = TVHPOLL_IN; ev[1].fd = spawn_pipe_error.rd; ev[1].data.ptr = &spawn_pipe_error; tvhpoll_add(efd, ev, 2); while (spawn_pipe_running) { nfds = tvhpoll_wait(efd, ev, 2, 500); if (nfds > 0) { spawn_pipe_read(&spawn_pipe_info, &spawn_info_buf, LOG_INFO); spawn_pipe_read(&spawn_pipe_error, &spawn_error_buf, LOG_ERR); } spawn_reaper(); } tvhpoll_destroy(efd); return NULL; }
static void * tcp_server_loop(void *aux) { int r; tvhpoll_event_t ev; tcp_server_t *ts; tcp_server_launch_t *tsl; pthread_attr_t attr; pthread_t tid; socklen_t slen; pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); while(1) { r = tvhpoll_wait(tcp_server_poll, &ev, 1, -1); if(r == -1) { perror("tcp_server: tchpoll_wait"); continue; } if (r == 0) continue; ts = ev.data.ptr; if(ev.events & TVHPOLL_HUP) { close(ts->serverfd); free(ts); continue; } if(ev.events & TVHPOLL_IN) { tsl = malloc(sizeof(tcp_server_launch_t)); tsl->start = ts->start; tsl->opaque = ts->opaque; slen = sizeof(struct sockaddr_storage); tsl->fd = accept(ts->serverfd, (struct sockaddr *)&tsl->peer, &slen); if(tsl->fd == -1) { perror("accept"); free(tsl); sleep(1); continue; } slen = sizeof(struct sockaddr_storage); if(getsockname(tsl->fd, (struct sockaddr *)&tsl->self, &slen)) { close(tsl->fd); free(tsl); continue; } pthread_create(&tid, &attr, tcp_server_start, tsl); } } return NULL; }
/* * Timeshift thread */ void *timeshift_reader ( void *p ) { timeshift_t *ts = p; int nfds, end, run = 1, wait = -1; timeshift_file_t *cur_file = NULL; int cur_speed = 100, keyframe_mode = 0; int64_t pause_time = 0, play_time = 0, last_time = 0; int64_t now, deliver, skip_time = 0; streaming_message_t *sm = NULL, *ctrl = NULL; timeshift_index_iframe_t *tsi = NULL; streaming_skip_t *skip = NULL; time_t last_status = 0; tvhpoll_t *pd; tvhpoll_event_t ev = { 0 }; pd = tvhpoll_create(1); ev.fd = ts->rd_pipe.rd; ev.events = TVHPOLL_IN; tvhpoll_add(pd, &ev, 1); /* Output */ while (run) { // Note: Previously we allowed unlimited wait, but we now must wake periodically // to output status message if (wait < 0 || wait > 1000) wait = 1000; /* Wait for data */ if(wait) nfds = tvhpoll_wait(pd, &ev, 1, wait); else nfds = 0; wait = -1; end = 0; skip = NULL; now = getmonoclock(); /* Control */ pthread_mutex_lock(&ts->state_mutex); if (nfds == 1) { if (_read_msg(NULL, ts->rd_pipe.rd, &ctrl) > 0) { /* Exit */ if (ctrl->sm_type == SMT_EXIT) { tvhtrace("timeshift", "ts %d read exit request", ts->id); run = 0; streaming_msg_free(ctrl); ctrl = NULL; /* Speed */ } else if (ctrl->sm_type == SMT_SPEED) { int speed = ctrl->sm_code; int keyframe; /* Bound it */ if (speed > 3200) speed = 3200; if (speed < -3200) speed = -3200; /* Ignore negative */ if (ts->ondemand && (speed < 0)) speed = cur_file ? speed : 0; /* Process */ if (cur_speed != speed) { /* Live playback */ if (ts->state == TS_LIVE) { /* Reject */ if (speed >= 100) { tvhlog(LOG_DEBUG, "timeshift", "ts %d reject 1x+ in live mode", ts->id); speed = 100; /* Set position */ } else { tvhlog(LOG_DEBUG, "timeshift", "ts %d enter timeshift mode", ts->id); timeshift_writer_flush(ts); pthread_mutex_lock(&ts->rdwr_mutex); if ((cur_file = timeshift_filemgr_get(ts, 1))) { cur_file->roff = cur_file->size; pause_time = cur_file->last; last_time = pause_time; } pthread_mutex_unlock(&ts->rdwr_mutex); } /* Buffer playback */ } else if (ts->state == TS_PLAY) { pause_time = last_time; /* Paused */ } else { } /* Check keyframe mode */ keyframe = (speed < 0) || (speed > 400); if (keyframe != keyframe_mode) { tvhlog(LOG_DEBUG, "timeshift", "using keyframe mode? %s", keyframe ? "yes" : "no"); keyframe_mode = keyframe; if (keyframe) { tsi = NULL; } } /* Update */ play_time = getmonoclock(); cur_speed = speed; if (speed != 100 || ts->state != TS_LIVE) ts->state = speed == 0 ? TS_PAUSE : TS_PLAY; tvhlog(LOG_DEBUG, "timeshift", "ts %d change speed %d", ts->id, speed); } /* Send on the message */ ctrl->sm_code = speed; streaming_target_deliver2(ts->output, ctrl); ctrl = NULL; /* Skip/Seek */ } else if (ctrl->sm_type == SMT_SKIP) { skip = ctrl->sm_data; switch (skip->type) { case SMT_SKIP_LIVE: if (ts->state != TS_LIVE) { /* Reset */ if (ts->full) { pthread_mutex_lock(&ts->rdwr_mutex); timeshift_filemgr_flush(ts, NULL); ts->full = 0; pthread_mutex_unlock(&ts->rdwr_mutex); } /* Release */ if (sm) streaming_msg_free(sm); /* Find end */ skip_time = 0x7fffffffffffffffLL; // TODO: change this sometime! } break; case SMT_SKIP_ABS_TIME: if (ts->pts_delta == 0) { tvhlog(LOG_ERR, "timeshift", "ts %d abs skip not possible no PTS delta", ts->id); skip = NULL; break; } /* -fallthrough */ case SMT_SKIP_REL_TIME: /* Convert */ skip_time = ts_rescale(skip->time, 1000000); tvhlog(LOG_DEBUG, "timeshift", "ts %d skip %"PRId64" requested %"PRId64, ts->id, skip_time, skip->time); /* Live playback (stage1) */ if (ts->state == TS_LIVE) { pthread_mutex_lock(&ts->rdwr_mutex); if ((cur_file = timeshift_filemgr_get(ts, !ts->ondemand))) { cur_file->roff = cur_file->size; last_time = cur_file->last; } else { tvhlog(LOG_ERR, "timeshift", "ts %d failed to get current file", ts->id); skip = NULL; } pthread_mutex_unlock(&ts->rdwr_mutex); } /* May have failed */ if (skip) { skip_time += (skip->type == SMT_SKIP_ABS_TIME) ? ts->pts_delta : last_time; tvhlog(LOG_DEBUG, "timeshift", "ts %d skip last_time %"PRId64" pts_delta %"PRId64, ts->id, skip_time - ts->pts_delta, ts->pts_delta); /* Live (stage2) */ if (ts->state == TS_LIVE) { if (skip_time >= now) { tvhlog(LOG_DEBUG, "timeshift", "ts %d skip ignored, already live", ts->id); skip = NULL; } else { ts->state = TS_PLAY; } } } /* OK */ if (skip) { /* Adjust time */ play_time = now; pause_time = skip_time; tsi = NULL; /* Clear existing packet */ if (sm) streaming_msg_free(sm); sm = NULL; } break; default: tvhlog(LOG_ERR, "timeshift", "ts %d invalid/unsupported skip type: %d", ts->id, skip->type); skip = NULL; break; } /* Error */ if (!skip) { ((streaming_skip_t*)ctrl->sm_data)->type = SMT_SKIP_ERROR; streaming_target_deliver2(ts->output, ctrl); ctrl = NULL; } /* Ignore */ } else { streaming_msg_free(ctrl); ctrl = NULL; } } } /* Status message */ if (now >= (last_status + 1000000)) { streaming_message_t *tsm; timeshift_status_t *status; timeshift_index_iframe_t *fst, *lst; status = calloc(1, sizeof(timeshift_status_t)); fst = _timeshift_first_frame(ts); lst = _timeshift_last_frame(ts); status->full = ts->full; status->shift = ts->state <= TS_LIVE ? 0 : ts_rescale_i(now - last_time, 1000000); if (lst && fst && lst != fst && ts->pts_delta != PTS_UNSET) { status->pts_start = ts_rescale_i(fst->time - ts->pts_delta, 1000000); status->pts_end = ts_rescale_i(lst->time - ts->pts_delta, 1000000); } else { status->pts_start = PTS_UNSET; status->pts_end = PTS_UNSET; } tsm = streaming_msg_create_data(SMT_TIMESHIFT_STATUS, status); streaming_target_deliver2(ts->output, tsm); last_status = now; } /* Done */ if (!run || !cur_file || ((ts->state != TS_PLAY && !skip))) { pthread_mutex_unlock(&ts->state_mutex); continue; } /* Calculate delivery time */ deliver = (now - play_time) + TIMESHIFT_PLAY_BUF; deliver = (deliver * cur_speed) / 100; deliver = (deliver + pause_time); /* Determine next packet */ if (!sm) { /* Rewind or Fast forward (i-frame only) */ if (skip || keyframe_mode) { timeshift_file_t *tsf = NULL; int64_t req_time; /* Time */ if (!skip) req_time = last_time + ((cur_speed < 0) ? -1 : 1); else req_time = skip_time; tvhlog(LOG_DEBUG, "timeshift", "ts %d skip to %"PRId64" from %"PRId64, ts->id, req_time - ts->pts_delta, last_time - ts->pts_delta); /* Find */ pthread_mutex_lock(&ts->rdwr_mutex); end = _timeshift_skip(ts, req_time, last_time, cur_file, &tsf, &tsi); pthread_mutex_unlock(&ts->rdwr_mutex); if (tsi) tvhlog(LOG_DEBUG, "timeshift", "ts %d skip found pkt @ %"PRId64, ts->id, tsi->time - ts->pts_delta); /* File changed (close) */ if ((tsf != cur_file) && cur_file && cur_file->rfd >= 0) { close(cur_file->rfd); cur_file->rfd = -1; } /* Position */ if (cur_file) cur_file->refcount--; if ((cur_file = tsf) != NULL) { if (tsi) cur_file->roff = tsi->pos; else cur_file->roff = 0; } } /* Find packet */ if (_timeshift_read(ts, &cur_file, &sm, &wait) == -1) { pthread_mutex_unlock(&ts->state_mutex); break; } } /* Send skip response */ if (skip) { if (sm && sm->sm_type == SMT_PACKET) { th_pkt_t *pkt = sm->sm_data; skip->time = pkt->pkt_pts; skip->type = SMT_SKIP_ABS_TIME; tvhlog(LOG_DEBUG, "timeshift", "ts %d skip to pts %"PRId64" ok, time %"PRId64, ts->id, ts_rescale(skip->time, 1000000), sm->sm_time - ts->pts_delta); } else { /* Report error */ skip->type = SMT_SKIP_ERROR; skip = NULL; tvhlog(LOG_DEBUG, "timeshift", "ts %d skip failed (%d)", ts->id, sm ? sm->sm_type : -1); } streaming_target_deliver2(ts->output, ctrl); ctrl = NULL; } /* Deliver */ if (sm && (skip || (((cur_speed < 0) && (sm->sm_time >= deliver)) || ((cur_speed > 0) && (sm->sm_time <= deliver))))) { if (sm->sm_type == SMT_PACKET && tvhtrace_enabled()) { th_pkt_t *pkt = sm->sm_data; tvhtrace("timeshift", "ts %d pkt out - stream %d type %c pts %10"PRId64 " dts %10"PRId64 " dur %10d len %6zu time %14"PRItime_t, ts->id, pkt->pkt_componentindex, pkt_frametype_to_char(pkt->pkt_frametype), ts_rescale(pkt->pkt_pts, 1000000), ts_rescale(pkt->pkt_dts, 1000000), pkt->pkt_duration, pktbuf_len(pkt->pkt_payload), sm->sm_time - ts->pts_delta); } last_time = sm->sm_time; streaming_target_deliver2(ts->output, sm); sm = NULL; wait = 0; } else if (sm) { if (cur_speed > 0) wait = (sm->sm_time - deliver) / 1000; else wait = (deliver - sm->sm_time) / 1000; if (wait == 0) wait = 1; tvhtrace("timeshift", "ts %d wait %d", ts->id, wait); } /* Terminate */ if (!cur_file || end != 0) { if (!end) end = (cur_speed > 0) ? 1 : -1; /* Back to live (unless buffer is full) */ if (end == 1 && !ts->full) { tvhlog(LOG_DEBUG, "timeshift", "ts %d eob revert to live mode", ts->id); ts->state = TS_LIVE; cur_speed = 100; ctrl = streaming_msg_create_code(SMT_SPEED, cur_speed); streaming_target_deliver2(ts->output, ctrl); ctrl = NULL; /* Flush timeshift buffer to live */ if (_timeshift_flush_to_live(ts, &cur_file, &sm, &wait) == -1) break; /* Close file (if open) */ if (cur_file && cur_file->rfd >= 0) { close(cur_file->rfd); cur_file->rfd = -1; } /* Flush ALL files */ if (ts->ondemand) timeshift_filemgr_flush(ts, NULL); /* Pause */ } else { if (cur_speed <= 0) { cur_speed = 0; ts->state = TS_PAUSE; } else { cur_speed = 100; ts->state = TS_PLAY; play_time = now; } tvhlog(LOG_DEBUG, "timeshift", "ts %d sob speed %d", ts->id, cur_speed); pause_time = last_time; ctrl = streaming_msg_create_code(SMT_SPEED, cur_speed); streaming_target_deliver2(ts->output, ctrl); ctrl = NULL; } /* Flush unwanted */ } else if (ts->ondemand && cur_file) { pthread_mutex_lock(&ts->rdwr_mutex); timeshift_filemgr_flush(ts, cur_file); pthread_mutex_unlock(&ts->rdwr_mutex); } pthread_mutex_unlock(&ts->state_mutex); } /* Cleanup */ tvhpoll_destroy(pd); if (cur_file && cur_file->rfd >= 0) { close(cur_file->rfd); cur_file->rfd = -1; } if (sm) streaming_msg_free(sm); if (ctrl) streaming_msg_free(ctrl); tvhtrace("timeshift", "ts %d exit reader thread", ts->id); return NULL; }
int tcp_connect(const char *hostname, int port, const char *bindaddr, char *errbuf, size_t errbufsize, int timeout) { int fd, r, res, err; struct addrinfo *ai; char portstr[6]; socklen_t errlen = sizeof(err); snprintf(portstr, 6, "%u", port); res = getaddrinfo(hostname, portstr, NULL, &ai); if (res != 0) { snprintf(errbuf, errbufsize, "%s", gai_strerror(res)); return -1; } fd = tvh_socket(ai->ai_family, SOCK_STREAM, 0); if(fd == -1) { snprintf(errbuf, errbufsize, "Unable to create socket: %s", strerror(errno)); freeaddrinfo(ai); return -1; } /** * Switch to nonblocking */ fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK); if (ai->ai_family == AF_INET || ai->ai_family == AF_INET6) { if (bindaddr && bindaddr[0] != '\0') { struct sockaddr_storage ip; memset(&ip, 0, sizeof(ip)); ip.ss_family = ai->ai_family; if (inet_pton(AF_INET, bindaddr, IP_IN_ADDR(ip)) <= 0 || bind(fd, (struct sockaddr *)&ip, IP_IN_ADDRLEN(ip)) < 0) { snprintf(errbuf, errbufsize, "Cannot bind to IPv%s addr '%s'", ai->ai_family == AF_INET6 ? "6" : "4", bindaddr); freeaddrinfo(ai); return -1; } } } else { snprintf(errbuf, errbufsize, "Invalid protocol family"); freeaddrinfo(ai); return -1; } r = connect(fd, ai->ai_addr, ai->ai_addrlen); freeaddrinfo(ai); if(r == -1) { /* timeout < 0 - do not wait at all */ if(errno == EINPROGRESS && timeout < 0) { err = 0; } else if(errno == EINPROGRESS) { tvhpoll_event_t ev; tvhpoll_t *efd; efd = tvhpoll_create(1); memset(&ev, 0, sizeof(ev)); ev.events = TVHPOLL_OUT; ev.fd = fd; ev.data.ptr = &fd; tvhpoll_add(efd, &ev, 1); /* minimal timeout is one second */ if (timeout < 1) timeout = 0; while (1) { if (!tvheadend_running) { errbuf[0] = '\0'; tvhpoll_destroy(efd); close(fd); return -1; } r = tvhpoll_wait(efd, &ev, 1, timeout * 1000); if (r > 0) break; if (r == 0) { /* Timeout */ snprintf(errbuf, errbufsize, "Connection attempt timed out"); tvhpoll_destroy(efd); close(fd); return -1; } if (!ERRNO_AGAIN(errno)) { snprintf(errbuf, errbufsize, "poll() error: %s", strerror(errno)); tvhpoll_destroy(efd); close(fd); return -1; } } tvhpoll_destroy(efd); getsockopt(fd, SOL_SOCKET, SO_ERROR, (void *)&err, &errlen); } else { err = errno; } } else { err = 0; } if(err != 0) { snprintf(errbuf, errbufsize, "%s", strerror(err)); close(fd); return -1; } fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) & ~O_NONBLOCK); /* Set the keep-alive active */ err = 1; setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (void *)&err, errlen); return fd; }
/* * Timeshift thread */ void *timeshift_reader ( void *p ) { timeshift_t *ts = p; int nfds, end, run = 1, wait = -1, state; timeshift_seek_t *seek = &ts->seek; timeshift_file_t *tmp_file; int cur_speed = 100, keyframe_mode = 0; int64_t mono_now, mono_play_time = 0, mono_last_status = 0; int64_t deliver, deliver0, pause_time = 0, last_time = 0, skip_time = 0; int64_t i64; streaming_message_t *sm = NULL, *ctrl = NULL; streaming_skip_t *skip = NULL; tvhpoll_t *pd; tvhpoll_event_t ev = { 0 }; pd = tvhpoll_create(1); ev.fd = ts->rd_pipe.rd; ev.events = TVHPOLL_IN; tvhpoll_add(pd, &ev, 1); /* Output */ while (run) { // Note: Previously we allowed unlimited wait, but we now must wake periodically // to output status message if (wait < 0 || wait > 1000) wait = 1000; /* Wait for data */ if(wait) nfds = tvhpoll_wait(pd, &ev, 1, wait); else nfds = 0; wait = -1; end = 0; skip = NULL; mono_now = getfastmonoclock(); /* Control */ pthread_mutex_lock(&ts->state_mutex); if (nfds == 1) { if (_read_msg(NULL, ts->rd_pipe.rd, &ctrl) > 0) { /* Exit */ if (ctrl->sm_type == SMT_EXIT) { tvhtrace(LS_TIMESHIFT, "ts %d read exit request", ts->id); run = 0; streaming_msg_free(ctrl); ctrl = NULL; /* Speed */ } else if (ctrl->sm_type == SMT_SPEED) { int speed = ctrl->sm_code; int keyframe; /* Bound it */ if (speed > 3200) speed = 3200; if (speed < -3200) speed = -3200; /* Ignore negative */ if (!ts->dobuf && (speed < 0)) speed = seek->file ? speed : 0; /* Process */ if (cur_speed != speed) { /* Live playback */ state = ts->state; if (state == TS_LIVE) { /* Reject */ if (speed >= 100) { tvhdebug(LS_TIMESHIFT, "ts %d reject 1x+ in live mode", ts->id); speed = 100; /* Set position */ } else { tvhdebug(LS_TIMESHIFT, "ts %d enter timeshift mode", ts->id); ts->dobuf = 1; _seek_reset(seek); tmp_file = timeshift_filemgr_newest(ts); if (tmp_file != NULL) { i64 = tmp_file->last; timeshift_file_put(tmp_file); } else { i64 = ts->buf_time; } seek->file = timeshift_filemgr_get(ts, i64); if (seek->file != NULL) { seek->file->roff = seek->file->size; pause_time = seek->file->last; last_time = pause_time; } else { pause_time = i64; last_time = pause_time; } } } /* Check keyframe mode */ keyframe = (speed < 0) || (speed > 400); if (keyframe != keyframe_mode) { tvhdebug(LS_TIMESHIFT, "using keyframe mode? %s", keyframe ? "yes" : "no"); keyframe_mode = keyframe; if (keyframe) seek->frame = NULL; } /* Update */ if (speed != 100 || state != TS_LIVE) { ts->state = speed == 0 ? TS_PAUSE : TS_PLAY; tvhtrace(LS_TIMESHIFT, "reader - set %s", speed == 0 ? "TS_PAUSE" : "TS_PLAY"); } if ((ts->state == TS_PLAY && state != TS_PLAY) || (speed != cur_speed)) { mono_play_time = mono_now; tvhtrace(LS_TIMESHIFT, "update play time TS_LIVE - %"PRId64" play buffer from %"PRId64, mono_now, pause_time); if (speed != cur_speed) pause_time = last_time; } else if (ts->state == TS_PAUSE && state != TS_PAUSE) { pause_time = last_time; } cur_speed = speed; tvhdebug(LS_TIMESHIFT, "ts %d change speed %d", ts->id, speed); } /* Send on the message */ ctrl->sm_code = speed; streaming_target_deliver2(ts->output, ctrl); ctrl = NULL; /* Skip/Seek */ } else if (ctrl->sm_type == SMT_SKIP) { skip = ctrl->sm_data; switch (skip->type) { case SMT_SKIP_LIVE: if (ts->state != TS_LIVE) { /* Reset */ if (ts->full) { timeshift_filemgr_flush(ts, NULL); _seek_reset(seek); ts->full = 0; } /* Release */ if (sm) streaming_msg_free(sm); /* Find end */ skip_time = 0x7fffffffffffffffLL; // TODO: change this sometime! } break; case SMT_SKIP_ABS_TIME: /* -fallthrough */ case SMT_SKIP_REL_TIME: /* Convert */ skip_time = ts_rescale(skip->time, 1000000); tvhdebug(LS_TIMESHIFT, "ts %d skip %"PRId64" requested %"PRId64, ts->id, skip_time, skip->time); /* Live playback (stage1) */ if (ts->state == TS_LIVE) { _seek_reset(seek); tmp_file = timeshift_filemgr_newest(ts); if (tmp_file) { i64 = tmp_file->last; timeshift_file_put(tmp_file); } if (tmp_file && (seek->file = timeshift_filemgr_get(ts, i64)) != NULL) { seek->file->roff = seek->file->size; last_time = seek->file->last; } else { last_time = ts->buf_time; } } /* May have failed */ if (skip->type == SMT_SKIP_REL_TIME) skip_time += last_time; tvhdebug(LS_TIMESHIFT, "ts %d skip time %"PRId64, ts->id, skip_time); /* Live (stage2) */ if (ts->state == TS_LIVE) { if (skip_time >= ts->buf_time - TIMESHIFT_PLAY_BUF) { tvhdebug(LS_TIMESHIFT, "ts %d skip ignored, already live", ts->id); skip = NULL; } else { ts->state = TS_PLAY; ts->dobuf = 1; tvhtrace(LS_TIMESHIFT, "reader - set TS_PLAY"); } } /* OK */ if (skip) { /* seek */ seek->frame = NULL; end = _timeshift_do_skip(ts, skip_time, last_time, seek); if (seek->frame) { pause_time = seek->frame->time; tvhtrace(LS_TIMESHIFT, "ts %d skip - play buffer from %"PRId64" last_time %"PRId64, ts->id, pause_time, last_time); /* Adjust time */ if (mono_play_time != mono_now) tvhtrace(LS_TIMESHIFT, "ts %d update play time skip - %"PRId64, ts->id, mono_now); mono_play_time = mono_now; /* Clear existing packet */ if (sm) { streaming_msg_free(sm); sm = NULL; } } else { skip = NULL; } } break; default: tvherror(LS_TIMESHIFT, "ts %d invalid/unsupported skip type: %d", ts->id, skip->type); skip = NULL; break; } /* Error */ if (!skip) { ((streaming_skip_t*)ctrl->sm_data)->type = SMT_SKIP_ERROR; streaming_target_deliver2(ts->output, ctrl); ctrl = NULL; } /* Ignore */ } else { streaming_msg_free(ctrl); ctrl = NULL; } } } /* Done */ if (!run || !seek->file || ((ts->state != TS_PLAY && !skip))) { if (mono_now >= (mono_last_status + sec2mono(1))) { timeshift_status(ts, last_time); mono_last_status = mono_now; } pthread_mutex_unlock(&ts->state_mutex); continue; } /* Calculate delivery time */ deliver0 = (mono_now - mono_play_time) + TIMESHIFT_PLAY_BUF; deliver = (deliver0 * cur_speed) / 100; deliver = (deliver + pause_time); tvhtrace(LS_TIMESHIFT, "speed %d now %"PRId64" play_time %"PRId64" deliver %"PRId64" deliver0 %"PRId64, cur_speed, mono_now, mono_play_time, deliver, deliver0); /* Determine next packet */ if (!sm) { /* Rewind or Fast forward (i-frame only) */ if (skip || keyframe_mode) { int64_t req_time; /* Time */ if (!skip) req_time = last_time + ((cur_speed < 0) ? -1 : 1); else req_time = skip_time; end = _timeshift_do_skip(ts, req_time, last_time, seek); } /* Clear old message */ if (sm) { streaming_msg_free(sm); sm = NULL; } /* Find packet */ if (_timeshift_read(ts, seek, &sm, &wait) == -1) { pthread_mutex_unlock(&ts->state_mutex); break; } } /* Send skip response */ if (skip) { if (sm) { /* Status message */ skip->time = ts_rescale_inv(sm->sm_time, 1000000); skip->type = SMT_SKIP_ABS_TIME; tvhdebug(LS_TIMESHIFT, "ts %d skip to pts %"PRId64" ok", ts->id, sm->sm_time); /* Update timeshift status */ timeshift_fill_status(ts, &skip->timeshift, sm->sm_time); mono_last_status = mono_now; } else { /* Report error */ skip->type = SMT_SKIP_ERROR; skip = NULL; tvhdebug(LS_TIMESHIFT, "ts %d skip failed (%d)", ts->id, sm ? sm->sm_type : -1); } streaming_target_deliver2(ts->output, ctrl); } else { streaming_msg_free(ctrl); } ctrl = NULL; /* Deliver */ if (sm && (skip || (((cur_speed < 0) && (sm->sm_time >= deliver)) || ((cur_speed > 0) && (sm->sm_time <= deliver))))) { last_time = sm->sm_time; if (!skip && keyframe_mode) /* always send status on keyframe mode */ timeshift_status(ts, last_time); timeshift_packet_log("out", ts, sm); streaming_target_deliver2(ts->output, sm); sm = NULL; wait = 0; } else if (sm) { if (cur_speed > 0) wait = (sm->sm_time - deliver) / 1000; else wait = (deliver - sm->sm_time) / 1000; if (wait == 0) wait = 1; tvhtrace(LS_TIMESHIFT, "ts %d wait %d speed %d sm_time %"PRId64" deliver %"PRId64, ts->id, wait, cur_speed, sm->sm_time, deliver); } /* Periodic timeshift status */ if (mono_now >= (mono_last_status + sec2mono(1))) { timeshift_status(ts, last_time); mono_last_status = mono_now; } /* Terminate */ if (!seek->file || end != 0) { /* Back to live (unless buffer is full) */ if ((end == 1 && !ts->full) || !seek->file) { tvhdebug(LS_TIMESHIFT, "ts %d eob revert to live mode", ts->id); cur_speed = 100; ctrl = streaming_msg_create_code(SMT_SPEED, cur_speed); streaming_target_deliver2(ts->output, ctrl); ctrl = NULL; tvhtrace(LS_TIMESHIFT, "reader - set TS_LIVE"); /* Flush timeshift buffer to live */ if (_timeshift_flush_to_live(ts, seek, &wait) == -1) { pthread_mutex_unlock(&ts->state_mutex); break; } ts->state = TS_LIVE; /* Close file (if open) */ _read_close(seek); /* Pause */ } else { if (cur_speed <= 0) { cur_speed = 0; tvhtrace(LS_TIMESHIFT, "reader - set TS_PAUSE"); ts->state = TS_PAUSE; } else { cur_speed = 100; tvhtrace(LS_TIMESHIFT, "reader - set TS_PLAY"); if (ts->state != TS_PLAY) { ts->state = TS_PLAY; ts->dobuf = 1; if (mono_play_time != mono_now) tvhtrace(LS_TIMESHIFT, "update play time (pause) - %"PRId64, mono_now); mono_play_time = mono_now; } } tvhdebug(LS_TIMESHIFT, "ts %d sob speed %d last time %"PRId64, ts->id, cur_speed, last_time); pause_time = last_time; ctrl = streaming_msg_create_code(SMT_SPEED, cur_speed); streaming_target_deliver2(ts->output, ctrl); ctrl = NULL; } } pthread_mutex_unlock(&ts->state_mutex); } /* Cleanup */ tvhpoll_destroy(pd); _read_close(seek); if (sm) streaming_msg_free(sm); if (ctrl) streaming_msg_free(ctrl); tvhtrace(LS_TIMESHIFT, "ts %d exit reader thread", ts->id); return NULL; }
/* * Discovery thread */ static void * upnp_thread( void *aux ) { char *bindaddr = aux; tvhpoll_t *poll = tvhpoll_create(2); tvhpoll_event_t ev[2]; upnp_data_t *data; udp_connection_t *multicast = NULL, *unicast = NULL; udp_connection_t *conn; unsigned char buf[16384]; upnp_service_t *us; struct sockaddr_storage ip; socklen_t iplen; size_t size; int r; multicast = udp_bind("upnp", "upnp_thread_multicast", "239.255.255.250", 1900, NULL, 32*1024); if (multicast == NULL || multicast == UDP_FATAL_ERROR) goto error; unicast = udp_bind("upnp", "upnp_thread_unicast", bindaddr, 0, NULL, 32*1024); if (unicast == NULL || unicast == UDP_FATAL_ERROR) goto error; memset(&ev, 0, sizeof(ev)); ev[0].fd = multicast->fd; ev[0].events = TVHPOLL_IN; ev[0].data.ptr = multicast; ev[1].fd = unicast->fd; ev[1].events = TVHPOLL_IN; ev[1].data.ptr = unicast; tvhpoll_add(poll, ev, 2); while (upnp_running && multicast->fd >= 0) { r = tvhpoll_wait(poll, ev, 2, 1000); while (r-- > 0) { if ((ev[r].events & TVHPOLL_IN) != 0) { conn = ev[r].data.ptr; iplen = sizeof(ip); size = recvfrom(conn->fd, buf, sizeof(buf), 0, (struct sockaddr *)&ip, &iplen); #if ENABLE_TRACE if (size > 0) { char tbuf[256]; inet_ntop(ip.ss_family, IP_IN_ADDR(ip), tbuf, sizeof(tbuf)); tvhtrace("upnp", "%s - received data from %s:%hu [size=%zi]", conn == multicast ? "multicast" : "unicast", tbuf, (unsigned short) IP_PORT(ip), size); tvhlog_hexdump("upnp", buf, size); } #endif /* TODO: a filter */ TAILQ_FOREACH(us, &upnp_services, us_link) us->us_received(buf, size, conn, &ip); } } while (1) { pthread_mutex_lock(&upnp_lock); data = TAILQ_FIRST(&upnp_data_write); if (data) TAILQ_REMOVE(&upnp_data_write, data, data_link); pthread_mutex_unlock(&upnp_lock); if (data == NULL) break; udp_write_queue(unicast, &data->queue, &data->storage); htsbuf_queue_flush(&data->queue); free(data); } } error: upnp_running = 0; tvhpoll_destroy(poll); udp_close(unicast); udp_close(multicast); return NULL; }