Git Product home page Git Product logo

Comments (1)

watfordjc avatar watfordjc commented on August 22, 2024

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>&ensp;
  <span id="weather-temp"></span></div><br>

  <div id="weather-location" class="middle">&ensp;</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":"&#xf002;","temp":"21&deg;C"},
{"location":"London","icon":"&#xf041;","temp":"21&deg;C"},
{"location":"Cardiff","icon":"&#xf008;","temp":"18&deg;C"},
{"location":"Belfast","icon":"&#xf017;","temp":"15&deg;C"},
{"location":"Glasgow","icon":"&#xf041;","temp":"15&deg;C"},
{"location":"Edinburgh","icon":"&#xf013;","temp":"14&deg;C"},
{"location":"Leeds","icon":"&#xf006;","temp":"20&deg;C"},
{"location":"Liverpool","icon":"&#xf002;","temp":"19&deg;C"},
{"location":"Birmingham","icon":"&#xf010;","temp":"20&deg;C"},
{"location":"Nottingham","icon":"&#xf002;","temp":"21&deg;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 = "&emsp;";
    document.getElementById("weather-temp").innerHTML = "&emsp;";
    document.getElementById("weather-location").innerHTML = "&emsp;";
    document.getElementById("copyright").innerHTML = "&emsp;<br>&emsp;";
    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>&ensp;
  <span id="weather-temp"></span></div><br>
  <div id="weather-location" class="middle">&ensp;</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:

Two clocks on a black background separated vertically with the same semi-transparent grey background horizontally aligned with each other. They both have white text, and both say the time is 21:37 UTC+1. The bottom clock also displays the weather for Nottingham (18 Celsius, partially cloudy night).

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

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:

Two clocks on a black background separated vertically with the same semi-transparent grey background horizontally aligned with each other. They both have white text, and both say the time is 05:07 UTC+1. The top clock says it is a clear night in Watford with a temperature of warm, powered by placeholder data. The bottom clock says it is a heavily raining day in Leeds, powered by Met Office Data.

The new user preferences (not available in UI) and their (default value) OBS source names are:

  • obs_local_clock_weather_symbol_source_name: LocalClockWeatherSymbol
  • obs_local_clock_weather_temp_source_name: LocalClockWeatherTemperature
  • obs_local_clock_location_source_name: LocalClockLocation
  • obs_local_clock_weather_attrib1_source_name: LocalClockWeatherAttribution1
  • obs_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 Show
  • obs_slideshow_next_scene_delay: integer number of seconds between slides. Default value: 30 seconds
  • obs_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":"&#xf002;","temp":"21&deg;C","tz":"Europe/London"},
{"location":"London","icon":"&#xf002;","temp":"23&deg;C","tz":"Europe/London"},
{"location":"Cardiff","icon":"&#xf041;","temp":"22&deg;C","tz":"Europe/London"},
{"location":"Belfast","icon":"&#xf017;","temp":"17&deg;C","tz":"Europe/London"},
{"location":"Glasgow","icon":"&#xf017;","temp":"17&deg;C","tz":"Europe/London"},
{"location":"Edinburgh","icon":"&#xf002;","temp":"16&deg;C","tz":"Europe/London"},
{"location":"Leeds","icon":"&#xf041;","temp":"19&deg;C","tz":"Europe/London"},
{"location":"Liverpool","icon":"&#xf002;","temp":"20&deg;C","tz":"Europe/London"},
{"location":"Birmingham","icon":"&#xf006;","temp":"20&deg;C","tz":"Europe/London"},
{"location":"Nottingham","icon":"&#xf002;","temp":"21&deg;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

YouTube Video: Stream Controller Simultaneously Updating OBS Sources

from csharp-stream-controller.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.