Comments (1)
Quartz.NET
The first thing I need is a timer that fires on the minute, every minute. If the second is the smallest unit I'm going to be using for synchronisation, I'm also going to need a separate timer that fires every second except on the zeroth and sixtieth second which will already be covered by the minute timer. My starting code is in commit 4039991.
Stream Clock - HTML
The next thing I need to do is probably going to be the most complex: changing my stream clock from HTML to native (OBS) controls. This is the current HTML for the clock:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>A simple clock</title>
<link rel="stylesheet" type="text/css" href="./css/clock.css?v=1" />
</head>
<body translate="no" >
<div id="output" class="top-left"></div><br>
<div class="middle"><span id="weather-symbol"></span> 
<span id="weather-temp"></span></div><br>
<div id="weather-location" class="middle"> </div><br>
<div id="copyright" class="bottom-left copyright">Powered by<br>Met Office Data</div>
<script src="./js/moment.min.js"></script>
<script src="./js/clock.js"></script>
</body>
</html>
clock.js
handles most of the control stuff.
Time
The current time is produced by moment.js
which is started using this function:
var c;
setInterval(
c = function() {
var format = "HH:mm UTC"
var offset = moment().format('Z');
if (offset.split(':')[1] == "00") {
offset = offset.split(':')[0];
}
if (offset.charAt(1) == '0') {
offset = offset.slice(0, 1) + offset.slice(2);
}
output.innerText = moment().format(format + offset || '');
}, 1000);
c();
That produces, for example 17:08 UTC+1
, and is updated approximately every second (not every UTC second, every ~1000 milliseconds after the function is first called).
The standard format of moment().format('Z')
for my timezone would be +01:00
, so I first strip off the minutes if the UTC offset is a round number of hours and then strip off up to one leading zero from the hours - UTC would be UTC+0, matching my datetime timezone formatting on Twitter as some people and operating systems are ignorant that UTC categorically never has daylight saving time.
There are two potential ways I can go with this. At the moment, all of the locations are in the same timezone so I can switch from per-second updates to per-minute updates (since Quartz.NET will fire on the minute). The other is to add timezone data to the JSON and support multiple timezones, making the time being displayed dependent on the location being displayed.
Location-dependent time would, for example, allow the code to be reused for (e.g.) split screening between two locations in different timezones and displaying a separate clock for each side of the screen.
Location and Weather
The next thing is the weather Powered by Met Office Data (mandatory statement for using their API).
The data is fetched from my server and updated using the following function in clock.js
:
var jsonData;
var d;
var n = 0;
setInterval(
d = function() {
fetch('./js/current-weather-uk.json', {cache: "reload"})
.then((response) => {
return response.json();
})
.then((data) => {
jsonData = data;
n = jsonData.length;
// console.log(data);
});
}, 60000);
d();
That fetches some JSON on start and then refreshes it every 60 seconds (could be a lot less often, my server only updates the file every 15-20 minutes). The data gets stored in the jsonData
variable and is an array that looks like this:
[
{"location":"Watford","icon":"","temp":"21°C"},
{"location":"London","icon":"","temp":"21°C"},
{"location":"Cardiff","icon":"","temp":"18°C"},
{"location":"Belfast","icon":"","temp":"15°C"},
{"location":"Glasgow","icon":"","temp":"15°C"},
{"location":"Edinburgh","icon":"","temp":"14°C"},
{"location":"Leeds","icon":"","temp":"20°C"},
{"location":"Liverpool","icon":"","temp":"19°C"},
{"location":"Birmingham","icon":"","temp":"20°C"},
{"location":"Nottingham","icon":"","temp":"21°C"}
]
Keeping to the API limits whilst leaving some wiggle room was a bit difficult given I wanted a general snapshot of the current UK weather. The 0th index is my location
, I then placed in the capital cities London, Cardiff, Belfast, and Edinburgh.
I then looked at the general geography of the UK and decided on a sort order based on geography: current location -> nearest capital city -> cycle through capital cities in a clockwise direction hitting any intermediary cities/towns on the way.
That's why Glasgow is between Belfast and Edinburgh: it is geographically between the two. Edinburgh to London wasn't so easy, so is based on the southbound route of the M1 motorway with an anti-clockwise loop from each town on the route linking back up to either another town or the M1. So, Leeds it the first place on that route, and then in an anti-clockwise circle you can hit Liverpool and Birmingham before getting back on the M1 at Nottingham. Keep going south and you get back to my location.
The API limits meant I couldn't cover big areas of the UK without refreshing the data less often (which, it should be noted, also feed into some home lighting controls), so the south, south-west and east coast of England aren't covered.
Next is the icon
(weather symbol) which is a font icon. When my backend converts the data from the Met Office Weather DataHub Global hourly spot data API into JSON, it converts the weather into the respective font icon equivalent.
#weather-symbol {
font-family: "Weather Icons";
font-style: normal;
font-size: 36px;
vertical-align: middle;
}
The icons used for the weather forecasts are licensed SIL OFL 1.1 and are available from Weather Icons.
The temp
is just the near-time temperature prediction for the location according to the Met Office.
Updating HTML
This is the function in clock.js
that cycles through the location data:
var i = 0;
var e;
setInterval(
e = function() {
console.log("Changing data..." + jsonData);
console.log("i = " + i + " n = " + n);
if (n == 0) {
document.getElementById("weather-symbol").innerHTML = " ";
document.getElementById("weather-temp").innerHTML = " ";
document.getElementById("weather-location").innerHTML = " ";
document.getElementById("copyright").innerHTML = " <br> ";
return;
}
if (i < n) {
document.getElementById("weather-symbol").innerHTML = jsonData[i].icon;
document.getElementById("weather-temp").innerHTML = jsonData[i].temp;
document.getElementById("weather-location").innerHTML = jsonData[i].location;
document.getElementById("copyright").innerHTML = "Powered by<br>Met Office Data";
i++;
}
console.log("i = " + i + " n = " + n);
if (i == n) {
i = 0;
}
console.log("i = " + i + " n = " + n);
}, 5000);
e();
i
is our standard integer variable name in for loops.
e
is our function that changes the weather data to the next location in the array.
n
is our standard integer variable name for the number of items in a collection/array.
If n == 0
, we don't have any data so we show some placeholder text. Most of it is em-width blank spaces but copyright
has a linebreak so the OBS source doesn't change size.
If i < n
we have some data and can display the next location in the array. We increase i
for the next time the timer fires.
If i
is now == n
, we set i
back to zero so the next time the timer fires it uses the first location in the array.
Stream Clock - Native
Existing HTML
This is the current HTML structure:
<div id="output" class="top-left"></div><br>
<div class="middle"><span id="weather-symbol"></span> 
<span id="weather-temp"></span></div><br>
<div id="weather-location" class="middle"> </div><br>
<div id="copyright" class="bottom-left copyright">Powered by<br>Met Office Data</div>
Existing CSS
This section covers the CSS for that structure, broken down into the relevant parts.
Semi-transparent grey background
html {
background-color: rgba(50,50,50,0.7);
}
Font and Text Colour, Min-Height for consistent look
body {
padding: 0;
margin: 0;
font: 24px monospace;
font-family:Open Sans;
min-height:180px;
color: rgba(255,255,255,1);
}
Min-Width for consistent look
div {
min-width: 160px;
}
Row styling
.top-left {
border-radius: 5px 0 0 0;
display: inline-block;
text-align:center;
padding: 5px 10px 2px 10px;
font-weight: 700;
}
.middle {
border-radius: 0;
display: inline-block;
text-align:center;
padding: 1px 10px 1px 10px;
line-height: 40px;
}
.bottom-left {
border-radius: 0 0 0 5px;
display: inline-block;
text-align:center;
padding: 5px 10px 5px 10px;
}
- Rounded corners on the top left and bottom left
- Center text for all rows
- Bold top row
- Line-Height for consistently sized middle rows (IIRC, fixes issue with weather symbol and temperature alignment)
Make the time bold
.time {
font-weight: 700;
}
Weather symbol Font and Alignment
#weather-symbol {
font-family: "Weather Icons";
font-style: normal;
font-size: 36px;
vertical-align: middle;
}
Temperature is bold
#weather-temp {
vertical-align: middle;
font-weight: 700;
}
Location is bold
#weather-location {
font-weight: 700;
}
API Attribution sizing
.copyright {
font-size: 18px;
line-height: 18px;
}
OBS LocalClock
I am going to start simple: I want an equivalent to <body></body>
.
- OBS Source Type : Group
- Name: LocalClock
- Transform: Position (1700, 900)*, Size (180, 180), Position Alignment (Top Left), Bounding Box Type (No bounds)
- OBS Source Type: Text (GDI+)
- Name: LocalClockTime
- Font: Open Sans Extra-Bold**
- Size: 34**
- Color: #ffffffff
- Text: 01:23 UTC+4
- Background Opacity: 0%
- Alignment: Center
- Transform: Position (0, 2)***, Bounding Box Type (Scale to height of bounds)***, Alignment in Bounding Box (Center), Bounding Box Size (180, 38)***
- Filters:
- Crop/Pad***: Left (0), Top (-2), Right (0), Left (-2)
- OBS Source Type: Color Source
- Name: ClockTransparency
- Color: #ff323232
- Width: 180
- Height: 180
- Filters
- Color Correction: Color (#ff323232, Opacity 85)
* Vertical position switching between 700 and 900 so it is either covering the existing clock or is out of the way but horizontally aligned with the existing clock.
** I am using 125% scaling on Windows, so a 24px CSS font should be 1.25x that. As 30px was too small (could've increased size by bounding box stretching), I went with 34px. Also changed font weight from 700 (Semi-Bold) to Extra-Bold.
*** To approximate CSS positioning and sizing.
Adjusting the text so it matches the current local time, I now have the following:
Updating the Time
The time is stored in a Text (GDI+) source called LocalClockTime.
Commit 6da211a changes ChronoTimer
so that it invokes an event on every clock minute (MinuteChanged
) and every clock second that is between 1 and 59 inclusive (SecondChanged
). It also does away with the previous clock properties/variables.
The commit also created the following user settings that are not currently accessible through the UI:
obs_local_clock_source_name
: a string containing the name of a single Text (GDI+) source for displaying the local time. Defaults to LocalClockTime.local_timezone_use_utc_offset
: a boolean of whether a UTC offset should be displayed (e.g. UTC+1) or if a local timezone abbreviation should be displayed (e.g. BST). Defaults to True.local_timezone_abbreviation
: a string containing the timezone abbreviation for local standard time (e.g. GMT). Defaults to an empty string, which will display ?????.local_timezone_dst_abbreviation
: a string containing the timezone abbreviation for local daylight time (e.g. BST). Defaults to an empty string, which will display ?????.
On launch, the commit has ObsWebsocketConnection
now calling UpdateTimezoneString()
which updates the abbreviation for the timezone. User preferences and Windows settings are not currently monitored for timezone and DST changes.
Finally, the commit also updates the text for the OBS source obs_local_clock_source_name
(LocalClockTime). When source properties are (automatically) obtained by ObsWebsocketConnection
, if the source's name matches obs_local_clock_source_name
then UpdateLocalClock()
is called immediately to change the OBS source's text, and UpdateLocalClock()
is added to the MinuteChanged
event handler.
YouTube Video: Stream Controller Updating OBS Source
Updating the Weather
The weather will require an additional 5 source items, all of type Text (GDI+). The attribution text is split on two lines because OBS doesn't appear to have a way to set line-height or line-spacing.
With the new sources added and positioned approximately in the same place as my HTML source, the group is as follows:
- OBS Source Type : Group
- Name: LocalClock
- Transform: Position (1700, 900), Size (180, 180), Position Alignment (Top Left), Bounding Box Type (No bounds)
- OBS Source Type: Text (GDI+)
- Name: LocalClockTime
- Font: Open Sans Extra-Bold
- Size: 34
- Color: #ffffffff
- Text: 01:23 UTC+4
- Background Opacity: 0%
- Alignment: Center
- Transform: Position (0, 2), Bounding Box Type (Scale to height of bounds), Alignment in Bounding Box (Center), Bounding Box Size (180, 38)
- Filters:
- Crop/Pad: Left (0), Top (-2), Right (0), Left (-2)
- OBS Source Type: Text (GDI+)
- Name: LocalClockWeatherSymbol
- Font: Weather Icons Regular
- Size: 34
- Color: #ffffffff
- Text: (U+F02E - Clear Night)
- Background Opacity: 0%
- Alignment: Center
- Transform: Position (20, 40), Bounding Box Type (Scale to height of bounds), Alignment in Bounding Box (Center), Bounding Box Size (70, 50)
- OBS Source Type: Text (GDI+)
- Name: LocalClockWeatherTemperature
- Font: Open Sans Extra-Bold
- Size: 36
- Color: #ffffffff
- Text: Warm
- Background Opacity: 0%
- Alignment: Center
- Transform: Position (80, 47), Bounding Box Type (Scale to height of bounds), Alignment in Bounding Box (Center), Bounding Box Size (80, 36)
- OBS Source Type: Text (GDI+)
- Name: LocalClockLocation
- Font: Open Sans Extra-Bold
- Size: 34
- Color: #ffffffff
- Text: Watford
- Background Opacity: 0%
- Alignment: Center
- Transform: Position (0, 90), Bounding Box Type (Scale to height of bounds), Alignment in Bounding Box (Center), Bounding Box Size (180, 34)
- OBS Source Type: Text (GDI+)
- Name: LocalClockWeatherAttribution1
- Font: Open Sans Semi-Bold
- Size: 24
- Color: #ffffffff
- Text: Powered By
- Background Opacity: 0%
- Alignment: Center
- Vertical Alignment: Center
- Transform: Position (0, 128), Bounding Box Type (Scale to height of bounds), Alignment in Bounding Box (Center), Bounding Box Size (180, 24)
- OBS Source Type: Text (GDI+)
- Name: LocalClockWeatherAttribution2
- Font: Open Sans Semi-Bold
- Size: 24
- Color: #ffffffff
- Text: Placeholder Data
- Background Opacity: 0%
- Alignment: Center
- Vertical Alignment: Center
- Transform: Position (0, 146), Bounding Box Type (Scale to height of bounds), Alignment in Bounding Box (Center), Bounding Box Size (180, 24)
- OBS Source Type: Color Source
- Name: ClockTransparency
- Color: #ff323232
- Width: 180
- Height: 180
- Filters
- Color Correction: Color (#ff323232, Opacity 85)
That looks like this:
The new user preferences (not available in UI) and their (default value) OBS source names are:
obs_local_clock_weather_symbol_source_name
: LocalClockWeatherSymbolobs_local_clock_weather_temp_source_name
: LocalClockWeatherTemperatureobs_local_clock_location_source_name
: LocalClockLocationobs_local_clock_weather_attrib1_source_name
: LocalClockWeatherAttribution1obs_local_clock_weather_attrib2_source_name
: LocalClockWeatherAttribution2
Additional user preferences (not available in UI):
obs_local_clock_weather_json_url
: string for JSON URL containing weather data (specific format, see below)obs_slideshow_source_name
: string for a slideshow name. Default value: Lower Third Slide Showobs_slideshow_next_scene_delay
: integer number of seconds between slides. Default value: 30 secondsobs_local_clock_cycle_delay
: integer number of seconds between weather locations. Default value: 5 seconds
The new JSON weather data format is as follows, modified to make WPF parsing easier (the tz value is not currently used):
{"weatherData":[
{"location":"Watford","icon":"","temp":"21°C","tz":"Europe/London"},
{"location":"London","icon":"","temp":"23°C","tz":"Europe/London"},
{"location":"Cardiff","icon":"","temp":"22°C","tz":"Europe/London"},
{"location":"Belfast","icon":"","temp":"17°C","tz":"Europe/London"},
{"location":"Glasgow","icon":"","temp":"17°C","tz":"Europe/London"},
{"location":"Edinburgh","icon":"","temp":"16°C","tz":"Europe/London"},
{"location":"Leeds","icon":"","temp":"19°C","tz":"Europe/London"},
{"location":"Liverpool","icon":"","temp":"20°C","tz":"Europe/London"},
{"location":"Birmingham","icon":"","temp":"20°C","tz":"Europe/London"},
{"location":"Nottingham","icon":"","temp":"21°C","tz":"Europe/London"}
]}
The weather API attribution is currently hard-coded.
The F20 key is hard-coded as the slideshow next slide key. obs-websocket does not currently have the ability to change slides, and the master branch of OBS Studio has had some movement - procedure handlers were added 8 days ago but the change was reverted 4 days ago.
YouTube Video: Stream Controller Simultaneously Updating OBS Sources
from csharp-stream-controller.
Related Issues (20)
- Set application default sound device HOT 3
- Application-specific audio channels/devices
- Set system sounds default sound device HOT 1
- Set application audio device preference back to default HOT 1
- Replace restrictive dependency
- Make internationalisation (i18n) possible
- System tray context menu is in wrong place HOT 3
- Validation error template has hard-coded colour HOT 3
- Decide how to deal with tabular name/value pairs
- Create an accessibility options tab/page in the preferences window
- Make a portable version
- Auth required not reported
- System tray icon unreliable
- Connection status icon can blink too fast
- Make obs-websocket connection persistent
- Fix memory leaks HOT 1
- Changes to preferences applied prematurely
- Add Tweet Sync Support HOT 10
- Disable Silent Audio on Idle
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from csharp-stream-controller.