Skip to content
This repository has been archived by the owner on Jun 13, 2020. It is now read-only.

Commit

Permalink
Add YouTube client.
Browse files Browse the repository at this point in the history
Obtain Live streaming channel Id.
  • Loading branch information
mp911de committed Nov 27, 2016
1 parent 9042ac6 commit f1cf5c8
Show file tree
Hide file tree
Showing 11 changed files with 268 additions and 50 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Expand Up @@ -142,3 +142,5 @@ rpi-rgb-led-matrix/text-example
rpi-rgb-led-matrix/led-matrix
heckenlights-messagebox-controller/temp
config.ini
oauth-credentials
client_secret.*
18 changes: 18 additions & 0 deletions heckenlights-backend/pom.xml
Expand Up @@ -156,6 +156,24 @@
<version>${quartz.version}</version>
</dependency>

<dependency>
<groupId>com.google.apis</groupId>
<artifactId>google-api-services-youtube</artifactId>
<version>v3-rev180-1.22.0</version>
</dependency>

<dependency>
<groupId>com.google.http-client</groupId>
<artifactId>google-http-client-jackson2</artifactId>
<version>1.20.0</version>
</dependency>

<dependency>
<groupId>com.google.oauth-client</groupId>
<artifactId>google-oauth-client-jetty</artifactId>
<version>1.20.0</version>
</dependency>

<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
Expand Down
@@ -1,11 +1,8 @@
package de.paluch.heckenlights;

import java.io.IOException;
import java.time.Clock;
import java.util.TimeZone;

import javax.xml.bind.JAXB;

import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
Expand All @@ -19,12 +16,11 @@
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
import org.springframework.core.io.Resource;

import com.google.common.collect.ImmutableSet;

import de.paluch.heckenlights.application.RuleService;
import de.paluch.heckenlights.model.RuleState;
import de.paluch.heckenlights.model.Rules;
import de.paluch.heckenlights.tracking.TrackingMDCFilter;

@Configuration
Expand Down Expand Up @@ -75,12 +71,7 @@ public FilterRegistrationBean contextFilterRegistrationBean() {
}

@Bean
Clock clock(Rules rules) {
return Clock.system(TimeZone.getTimeZone(rules.getTimezone()).toZoneId());
}

@Bean
Rules rules(@Value("${rules.location}") Resource ruleLocation) throws IOException {
return JAXB.unmarshal(ruleLocation.getFile(), Rules.class);
Clock clock(RuleService ruleService) {
return Clock.system(TimeZone.getTimeZone(ruleService.getRules().getTimezone()).toZoneId());
}
}
@@ -1,16 +1,11 @@
package de.paluch.heckenlights.application;

import javax.xml.bind.JAXB;

import org.apache.log4j.Logger;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.Resource;

import de.paluch.heckenlights.model.Rules;

/**
* @author <a href="mailto:mpaluch@paluch.biz">Mark Paluch</a>
Expand All @@ -22,41 +17,23 @@ public class RefreshRulesJob implements Job {

private Logger log = Logger.getLogger(getClass());

private volatile long lastModified;

@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {

try {
ApplicationContext context = (ApplicationContext) jobExecutionContext.getScheduler().getContext()
.get(APPLICATION_CONTEXT_KEY);

Resource ruleLocation = context.getResource(context.getEnvironment().getProperty("rules.location"));
Rules rules = context.getBean(Rules.class);
RuleService rules = context.getBean(RuleService.class);

if (lastModified == 0) {
lastModified = ruleLocation.lastModified();
}

if (lastModified != ruleLocation.lastModified()) {
if (rules.isChanged()) {

log.info("Updating Rules");

Rules updated = JAXB.unmarshal(ruleLocation.getFile(), Rules.class);
updateRules(rules, updated);

rules.updateRules();
}

} catch (Exception e) {
log.warn(e.getMessage(), e);
}
}

private void updateRules(Rules instance, Rules updated) {
instance.getRules().clear();
instance.getRules().addAll(updated.getRules());

instance.setDefaultAction(updated.getDefaultAction());
instance.setTimeunit(updated.getTimeunit());
instance.setTimezone(updated.getTimezone());
}
}
Expand Up @@ -25,7 +25,7 @@ public class ResolveRule {
RuleState ruleState;

@NonNull
Rules rules;
RuleService ruleService;

@NonNull
Clock clock;
Expand All @@ -34,6 +34,8 @@ public Rule getRule() {

LocalTime zonedDateTime = LocalTime.now(clock);

Rules rules = ruleService.getRules();

for (Rule rule : rules.getRules()) {
int hour = zonedDateTime.getHour();
int minute = zonedDateTime.getMinute();
Expand Down
@@ -0,0 +1,76 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.paluch.heckenlights.application;

import java.io.IOException;
import java.util.Collection;

import javax.xml.bind.JAXB;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component;

import de.paluch.heckenlights.model.Rule;
import de.paluch.heckenlights.model.Rules;

/**
* @author Mark Paluch
*/
@Component
public class RuleService {

private final Resource ruleLocation;
private final Rules rules = new Rules();

private long lastModified;

public RuleService(@Value("${rules.location}") Resource ruleLocation) throws IOException {

this.ruleLocation = ruleLocation;
this.lastModified = ruleLocation.lastModified();

updateRules();
}

public boolean isChanged() {
try {
return lastModified != ruleLocation.lastModified();
} catch (IOException e) {
return false;
}
}

public void updateRules() throws IOException {

Rules updated = JAXB.unmarshal(ruleLocation.getFile(), Rules.class);
lastModified = ruleLocation.lastModified();
updateRules(rules, updated);
}

public final Rules getRules() {
return rules;
}

private void updateRules(Rules instance, Rules updated) {
instance.getRules().clear();
instance.getRules().addAll(updated.getRules());

instance.setDefaultAction(updated.getDefaultAction());
instance.setTimeunit(updated.getTimeunit());
instance.setTimezone(updated.getTimezone());
}
}
@@ -0,0 +1,115 @@
package de.paluch.heckenlights.client;

import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import com.google.api.client.auth.oauth2.Credential;
import com.google.api.client.auth.oauth2.StoredCredential;
import com.google.api.client.extensions.java6.auth.oauth2.AuthorizationCodeInstalledApp;
import com.google.api.client.extensions.jetty.auth.oauth2.LocalServerReceiver;
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow;
import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.client.util.store.DataStore;
import com.google.api.client.util.store.FileDataStoreFactory;
import com.google.api.services.youtube.YouTube;
import com.google.api.services.youtube.model.LiveBroadcastListResponse;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;

import lombok.extern.slf4j.Slf4j;

/**
* @author Mark Paluch
*/
@Component
@Slf4j
public class YouTubeClient {

private static final HttpTransport HTTP_TRANSPORT = new NetHttpTransport();
private static final JsonFactory JSON_FACTORY = new JacksonFactory();
private static final String CREDENTIALS_DIRECTORY = "oauth-credentials";
private static final List<String> scopes = Collections.singletonList("https://www.googleapis.com/auth/youtube.readonly");
private final GoogleClientSecrets clientSecrets;
private final LocalServerReceiver localReceiver;
private final LoadingCache<String, String> cache;

public YouTubeClient(@Value("${server.port}") int serverPort) throws IOException {

File file = new File("client_secret.json");

if (file.exists()) {
clientSecrets = GoogleClientSecrets.load(JSON_FACTORY, new FileReader(file));
localReceiver = new LocalServerReceiver.Builder().setPort(serverPort + 10).build();
} else {

log.warn("No {}, YouTubeClient disabled", file);

clientSecrets = null;
localReceiver = null;
}

cache = CacheBuilder.newBuilder().expireAfterWrite(1, TimeUnit.HOURS).build(new CacheLoader<String, String>() {
@Override
public String load(String s) throws Exception {
return fetchStreamingId();
}
});
}

public Credential authorize() throws IOException {

FileDataStoreFactory fileDataStoreFactory = new FileDataStoreFactory(new File(CREDENTIALS_DIRECTORY));
DataStore<StoredCredential> datastore = fileDataStoreFactory.getDataStore("listbroadcasts");

GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(HTTP_TRANSPORT, JSON_FACTORY, clientSecrets,
scopes).setCredentialDataStore(datastore).build();

// Authorize.
return new AuthorizationCodeInstalledApp(flow, localReceiver).authorize("user");
}

public String getYouTubeStreamingId() {

try {
return cache.get("youtube-streaming-id");
} catch (ExecutionException e) {
throw new IllegalStateException(e.getCause());
}
}

private String fetchStreamingId() throws IOException {

// This object is used to make YouTube Data API requests.
YouTube youtube = new YouTube.Builder(HTTP_TRANSPORT, JSON_FACTORY, authorize()).setApplicationName("heckenlights")
.build();

// Create a request to list broadcasts.
YouTube.LiveBroadcasts.List liveBroadcastRequest = youtube.liveBroadcasts().list("id,status");

// Indicate that the API response should not filter broadcasts
// based on their status.
liveBroadcastRequest.setBroadcastStatus("active");
liveBroadcastRequest.setBroadcastType("persistent");

LiveBroadcastListResponse response = liveBroadcastRequest.execute();

if (response.getItems().isEmpty()) {
return "";
}

return response.getItems().get(0).getId();
}
}

0 comments on commit f1cf5c8

Please sign in to comment.