Code Monkey home page Code Monkey logo

live-streaming-api's People

Contributors

oliverspryn avatar

Stargazers

 avatar  avatar  avatar

Watchers

 avatar

live-streaming-api's Issues

Standardize the Start Endpoint

As a user, I need a robust way to validate the information coming into the /start endpoint and more information in the reply to report on the status of the request so that I can better understand what went on during a start operation.

Acceptance Criteria:

  • GIVEN a request to start one or more live events and the live streaming endpoint
    • WHEN making the request
      • THEN the endpoint does not change
      • AND the query parameters do not change
    • WHEN not provided with the name of the streaming endpoint
      • THEN throw a 400 Bad Request error
      • AND stop further processing
      • AND create a JSON object, as follows:
        {
            "status": 400,
            "type": "InputValidationException",
            "message": "Input requires the name of a streaming endpoint",
            "developerMessage": "The query parameter 'endpoint' is required and must be the name of an existing streaming endpoint"
        }
        
    • WHEN not provided with the name of at least one or more live events (comma separated)
      • THEN throw a 400 Bad Request error
      • AND stop further processing
      • AND create a JSON object, as follows:
        {
            "status": 400,
            "type": "InputValidationException",
            "message": "Input requires the name of one or more live events",
            "developerMessage": "The query parameter 'events' is required and must be a comma-separated list of names of existing live events"
        }
        
    • WHEN not provided with the name of the streaming endpoint and the name of at least one or more live events (comma separated)
      • THEN throw a 400 Bad Request error
      • AND stop further processing
      • AND create a JSON object, as follows:
        {
            "status": 400,
            "type": "InputValidationException",
            "message": "Input requires the name of a streaming endpoint and the name of one or more live events",
            "developerMessage": "The query parameter 'endpoint' is required and must be the name of an existing streaming endpoint, and another query parameter 'events' is also required and must be a comma-separated list of names of existing live events"
        }
        
    • WHEN given a streaming endpoint, but it does not exist
      • THEN throw a 400 Bad Request error
      • AND stop further processing
      • AND create a JSON object, as follows:
        {
            "status": 400,
            "type": "ServiceValidationException",
            "message": "The given streaming endpoint does not exist",
            "developerMessage": "The query parameter 'endpoint' does not match the name of any existing streaming endpoints"
        }
        
    • WHEN given at least one live event but at least one of those events does not exist
      • THEN throw a 400 Bad Request error
      • AND stop further processing
      • AND create a JSON object, as follows:
        {
            "status": 400,
            "type": "ServiceValidationException",
            "message": "The following live event(s) do not exist: <comma separated list of invalid events>",
            "developerMessage": "The following live event(s) in the query parameter 'events' does not match the name(s) of any the existing live events: <comma separated list of invalid events>"
        }
        
    • WHEN given a streaming endpoint and at least one live event and all of those resources exist
      • THEN it starts the given streaming endpoint and all live events, as before
    • WHEN a streaming endpoint is not in a stopped state
      • THEN it skips starting the endpoint
    • WHEN any given live event is not in a stopped state
      • THEN it skips starting that endpoint, but attempts to start all the rest, if the above criteria is satsified
    • WHEN the operation completes
      • THEN return a 201 Created response
      • AND return the status of the endpoint and all given live events, just like the /status endpoint
      • AND return the diff between the pre-start service statuses and the post-start statuses, altogether, like this:
        {
            "changes": {
                "endpoint": {
                    "name": "<name of endpoint>",
                    "newStatus": "<status after start request>",
                    "oldStatus": "<status before start request>"
                },
                "events": [{
                    "name": "<name of event>",
                    "newStatus": "<status after start request>",
                    "oldStatus": "<status before start request>"
                 }, {
                    // Additional events here
                 }]
            },
            "status": {
                "summary": "<correct status>",
                "endpoint": {
                    "name": "<name of endpoint>",
                    "status": "<correct status>"
                },
                "events": [{
                    "name": "<name of event>",
                    "status": "<correct status>"
                }, {
                    // Additional events here
                }]
            }
        }
        

Developer Notes:
The resulting status may not be equal to running at the end of a start operation request. If for any reason, any existing endpoint or live event is not in the stopped state, they remain unchanged. For example, if one live event was in the process of stopping when the start request happened, that live event will not be changed by the application and will result in a stopping status, as defined by #13.

Remove Sentry

Instead of relying on a third party, like Sentry, for monitoring, we are moving completely to Azure Application Insights (per #60). This story is to remove all Sentry libraries and related implementation details. Application Insights should remain in place.

For future consideration, here are some resources for using Application Insights to do what Sentry was doing:

Determine the Service Status

As a user, I need to know the status of my live streaming services so that I know when I can start to stream or when everything is shut down and I am no longer being billed.

Acceptance Criteria:

  • GIVEN a request to list the status of one or more live events and the live streaming endpoint
    • WHEN a request is made
      • THEN the endpoint is /status
      • AND the access type is authenticated
      • AND the live events are passed in as a query parameter called events=event1,event2
      • AND the streaming endpoint is passed as a query parameter called endpoint=name
    • WHEN not provided with the name of the streaming endpoint
      • THEN throw a 400 Bad Request error
      • AND stop further processing
      • AND create a JSON object, as follows:
        {
            "status": 400,
            "type": "InputValidationException",
            "message": "Input requires the name of a streaming endpoint",
            "developerMessage": "The query parameter 'endpoint' is required and must be the name of an existing streaming endpoint"
        }
        
    • WHEN not provided with the name of at least one or more live events (comma separated)
      • THEN throw a 400 Bad Request error
      • AND stop further processing
      • AND create a JSON object, as follows:
        {
            "status": 400,
            "type": "InputValidationException",
            "message": "Input requires the name of one or more live events",
            "developerMessage": "The query parameter 'events' is required and must be a comma-separated list of names of existing live events"
        }
        
    • WHEN not provided with the name of the streaming endpoint and the name of at least one or more live events (comma separated)
      • THEN throw a 400 Bad Request error
      • AND stop further processing
      • AND create a JSON object, as follows:
        {
            "status": 400,
            "type": "InputValidationException",
            "message": "Input requires the name of a streaming endpoint and the name of one or more live events",
            "developerMessage": "The query parameter 'endpoint' is required and must be the name of an existing streaming endpoint, and another query parameter 'events' is also required and must be a comma-separated list of names of existing live events"
        }
        
    • WHEN given a streaming endpoint, but it does not exist
      • THEN throw a 400 Bad Request error
      • AND stop further processing
      • AND create a JSON object, as follows:
        {
            "status": 400,
            "type": "ServiceValidationException",
            "message": "The given streaming endpoint does not exist",
            "developerMessage": "The query parameter 'endpoint' does not match the name of any existing streaming endpoints"
        }
        
    • WHEN given at least one live event but at least one of those events does not exist
      • THEN throw a 400 Bad Request error
      • AND stop further processing
      • AND create a JSON object, as follows:
        {
            "status": 400,
            "type": "ServiceValidationException",
            "message": "The following live event(s) do not exist: <comma separated list of invalid events>",
            "developerMessage": "The following live event(s) in the query parameter 'events' does not match the name(s) of any the existing live events: <comma separated list of invalid events>"
        }
        
    • WHEN given a streaming endpoint and at least one live event and all of those resources exist
      • THEN it checks the status of the streaming endpoint and all given live events
    • WHEN the streaming endpoint and all live events are stopped
      • THEN return a 200 OK response
      • AND create a JSON object, as follows:
        {
            "summary": "stopped",
            "endpoint": {
                "name": "<name of endpoint>",
                "status": "stopped"
            },
            "events": [{
                "name": "<name of event>",
                "status": "stopped"
            }, {
                // Addtional events here
            }]
        }
        
    • WHEN either the streaming endpoint or any live event is starting
      • THEN return a 200 OK response
      • AND create a JSON object, as follows:
        {
            "summary": "starting",
            "endpoint": {
                "name": "<name of endpoint>",
                "status": "<starting or whatever status it's currently in>"
            },
            "events": [{
                "name": "<name of event>",
                "status": "<starting or whatever status it's currently in>"
            }, {
                // Addtional events here
            }]
        }
        
    • WHEN the streaming endpoint and all live events are running
      • THEN return a 200 OK response
      • AND create a JSON object, as follows:
        {
            "summary": "running",
            "endpoint": {
                "name": "<name of endpoint>",
                "status": "running"
            },
            "events": [{
                "name": "<name of event>",
                "status": "running"
            }, {
                // Addtional events here
            }]
        }
        
    • WHEN either the streaming endpoint or any live event is stopping
      • THEN return a 200 OK response
      • AND create a JSON object, as follows:
        {
            "summary": "stopping",
            "endpoint": {
                "name": "<name of endpoint>",
                "status": "<stopping or whatever status it's currently in>"
            },
            "events": [{
                "name": "<name of event>",
                "status": "<stopping or whatever status it's currently in>"
            }, {
                // Addtional events here
            }]
        }
        
    • WHEN either the streaming endpoint or any live event is scaling and all of the rest are either scaling or running
      • THEN return a 200 OK response
      • AND create a JSON object, as follows:
        {
            "summary": "running",
            "endpoint": {
                "name": "<name of endpoint>",
                "status": "<running or scaling>"
            },
            "events": [{
                "name": "<name of event>",
                "status": "<running or scaling>"
            }, {
                // Addtional events here
            }]
        }
        
    • WHEN either the streaming endpoint or any live event is deleting and all of the rest are either deleting or stopped
      • THEN return a 200 OK response
      • AND create a JSON object, as follows:
        {
            "summary": "stopped",
            "endpoint": {
                "name": "<name of endpoint>",
                "status": "<stopped or deleting>"
            },
            "events": [{
                "name": "<name of event>",
                "status": "<stopped or deleting>"
            }, {
                // Addtional events here
            }]
        }
        
    • WHEN either the streaming endpoint or any live event is in an error state
      • THEN return a 200 OK response
      • AND create a JSON object, as follows:
        {
            "summary": "error",
            "endpoint": {
                "name": "<name of endpoint>",
                "status": "<error or whatever status it's currently in>"
            },
            "events": [{
                "name": "<name of event>",
                "status": "<error or whatever status it's currently in>"
            }, {
                // Addtional events here
            }]
        }
        
    • WHEN any of the above combinations are not satisfied
      • THEN return a 200 OK response
      • AND create a JSON object, as follows:
        {
            "summary": "error",
            "endpoint": {
                "name": "<name of endpoint>",
                "status": "<current state>"
            },
            "events": [{
                "name": "<name of event>",
                "status": "<current state>"
            }, {
                // Addtional events here
            }]
        }
        

Developer Notes:
Here are the business rules behind each state.

Azure Status Controller Summary Status Description When Controller Should Report
Stopped Stopped Service is stopped When all streaming endpoints and live events are stopped
Starting Starting Service is starting When any streaming endpoint or live event is starting, regardless of the status of others
Running Running Service is running When all streaming endpoints and live events are running
Stopping Stopping Service is stopping When any streaming endpoint or live event is stopping, regardless of the status of others
Scaling Running Service is running and scaling up or down When any streaming endpoint or live event is scaling, and all of the other resources are also in a scaling or running state
Deleting Stopped Service is stopped and is being removed When any streaming endpoint or live event is deleting, and all of the other resources are also in a deleting or stopped state
N/A Error Any of the above combinations are not satisfied Any of the above combinations are not satisfied

Webhook Environment Variables Should Be Optional

When evaluating the configuration model, the webhook service is able to exit early whenever it determines that a particular webhook is null. The configuration service should be returning null for webhook environment variables that do not exist. However, it never makes that check and assumes those variables are always set.

This bug should fix that issue, returning null in the configuration model for any of the four webhooks whenever the corresponding environment variable does not exist.

Add Application Insights Event Tracking

As a user, I need page view tracking so that I know how often my application is utilized.

Acceptance Criteria:

  • GIVEN a running application
    • WHEN the locators page is viewed (regardless of a success or failure condition)
      • THEN a page tracking event called Locators is logged to Application Insights
    • WHEN the status page is viewed (regardless of a success or failure condition)
      • THEN a page tracking event called Status is logged to Application Insights
    • WHEN the start page is viewed (regardless of a success or failure condition)
      • THEN a page tracking event called Start is logged to Application Insights
    • WHEN the stop page is viewed (regardless of a success or failure condition)
      • THEN a page tracking event called Stop is logged to Application Insights

Stop a Live Stream

As a user, I need to stop the Azure Media services so that I am not charged for resources I am not using.

Acceptance Criteria:

  • GIVEN a request to stop the live streaming services
    • WHEN not provided with the name of at least one or more live events (comma separated)
      • THEN throw a 400 Bad Request error
      • AND create a JSON object, as follows:
        {
            "message": "Input requires the name of one or more live events"
        }
        
    • WHEN not provided with the name of the streaming endpoint
      • THEN throw a 400 Bad Request error
      • AND create a JSON object, as follows:
        {
            "message": "Input requires the name of a streaming endpoint"
        }
        
    • WHEN not provided with the name of the streaming endpoint and the name of at least one or more live events (comma separated)
      • THEN throw a 400 Bad Request error
      • AND create a JSON object, as follows:
        {
            "message": "Input requires the name of a streaming endpoint and the name of one or more live events"
        }
        
    • WHEN provided with the name of one or more live events (comma separated) and a streaming endpoint to stop
      • THEN the streaming endpoint is stopped
      • THEN the live event(s) are stopped
      • THEN all of the locator(s) associated with each asset are deleted (can be more than one)
      • THEN the asset associated with each live event is deleted
      • THEN all of the live output(s) associated with each live event are deleted (can be more than one)
    • WHEN the process is finished
      • THEN return a 200 OK response
      • AND create a JSON object, as follows:
        {
            "message": "Shut down"
        }
        
    • WHEN encountering an error with this process
      • THEN throw a 500 Internal Server error
      • AND create a JSON object, as follows:
        {
            "message": "An internal error occurred. Check the logs."
        }
        

Add Sentry Logging & Error Reporting

As a system administrator, I need the application to log and report errors so that I can understand the health of my system.

Acceptance Criteria:

  • GIVEN a deployed application
    • WHEN any of the endpoints are hit
      • THEN Sentry is initialized
      • AND the SENTRY_DSN environment variable is set to the ingest URL provided by Sentry
      • AND the SENTRY_ENVIRONMENT environment variable is set to prod for production and dev for development
      • AND the SENTRY_RELEASE environment variable is set to [email protected] where x.y.z is the version of the application
    • WHEN an exception is thrown
      • THEN the exception is logged to Sentry and the Azure Functions logger
      • AND the exception is still handled in the same manner as before
    • WHEN processing a request (whether it is successful or not)
      • THEN Sentry breadcrumbs are logged
      • THEN the message values for these breadcrumbs are logged to the Azure Functions logger
    • WHEN finished loading the environment variable configuration data
      • THEN a Sentry breadcrumb of the category type of bootstrapping is logged
      • THEN the message values for these breadcrumbs are logged to the Azure Functions logger
    • WHEN finished building the authorization client to Azure
      • THEN a Sentry breadcrumb of the category type of bootstrapping is logged
      • THEN the message values for these breadcrumbs are logged to the Azure Functions logger
    • WHEN beginning to validate the input data
      • THEN a Sentry breadcrumb of the category type of validation is logged
      • THEN the message values for these breadcrumbs are logged to the Azure Functions logger
    • WHEN finished locally validating the input data
      • THEN a Sentry breadcrumb of the category type of validation is logged
      • THEN the message values for these breadcrumbs are logged to the Azure Functions logger
    • WHEN finished validating the input data against the service available on Azure
      • THEN a Sentry breadcrumb of the category type of validation is logged
      • THEN the message values for these breadcrumbs are logged to the Azure Functions logger
    • WHEN starting the Azure services
      • THEN a Sentry breadcrumb of the category type of start is logged at each step along the way
      • THEN the message values for these breadcrumbs are logged to the Azure Functions logger
    • WHEN stopping the Azure services
      • THEN a Sentry breadcrumb of the category type of stop is logged at each step along the way
      • THEN the message values for these breadcrumbs are logged to the Azure Functions logger
    • WHEN checking the status of the Azure services
      • THEN a Sentry breadcrumb of the category type of status is logged at each step along the way
      • THEN the message values for these breadcrumbs are logged to the Azure Functions logger
    • WHEN retrieving the locators for the Live Events
      • THEN a Sentry breadcrumb of the category type of locator is logged at each step along the way
      • THEN the message values for these breadcrumbs are logged to the Azure Functions logger
    • WHEN calling the webhook(s)
      • THEN a Sentry breadcrumb of the category type of webhook is logged whenever the given
        webhook URL is null
      • THEN a Sentry breadcrumb of the category type of webhook is logged whenever a call to the given webhook is successful
      • THEN the message values for these breadcrumbs are logged to the Azure Functions logger

Developer Notes:
For the workflow classes (e.g. Start, Stop, etc...) logging at each step of the way looks something like this:

  • When the process starts
  • At the end
  • after each successful call to Azure (e.g. shut down the Live Event, deleted Asset, deleted the Live Output, etc...)
  • Whenever a process is skipped (e.g. service is already stated, skipping trying to start it again)

Feel free to adjust and expand this list, as necessary.

Migrate to Microsoft Authentication Library

Microsoft has begun the deprecation process of the legacy Azure Active Directory Authentication Library (ADAL) and recommends moving to the Microsoft Authentication Library (MSAL). As of June 30th, 2020, no new features will be added to ADAL, and support for ADAL formally ends on June 20th, 2022.

To quote their documentation: MSAL is now the recommended authentication library for use with the Microsoft identity platform.

Relevant links:

Service Status Summary Reports Incorrect Values

In some cases where the values of services are fluctuating during startup and shut down, the API may report incorrect service summaries if queried during this time of fluctuation. Here are the changes which need to be made:

  • Report starting whenever the endpoint OR any live event is starting
  • Report stopping whenever the endpoint OR any live event is stopping
  • Report running whenever the endpoint is either running or scaling and ALL live events are either running or scaling
  • Report stopped whenever the endpoint is either stopped or deleting and ALL live events are either stopped or deleting

Change HTTP Verb on the Stop Endpoint

As a user, I need the API to follow good REST patterns so that I can build a standards-compliant front-end application to consume its data.

Acceptance Criteria:

  • GIVEN the /stop endpoint
    • WHEN making a call to it
      • THEN the necessary HTTP verb is DELETE

Developer notes:
The HTTP verbs used on all other endpoints should remain the same.

Upgrade from .NET 5 to .NET 6

After the previous upgrade story was completed #51, .NET has hit stable. That is the first version of the unified .NET library to be an LTS version. This project should use it as soon as possible and address any compatibility issues which arise.

Update the Status to Report State Types

As a user, I need to know whether or not a given state is stable or transient so that I can build my frontend application to either consider a given state as unchanging (stable) or changing (transient).

Acceptance Criteria:

  • GIVEN any call with a status in the reply
    • WHEN returning the status of a resource (such as a live event, streaming endpoint, or summary)
      • THEN each summary object in the reply shows as follows:
        "summary": { "name": "deleting|error|running|scaling|starting|stopped|stopping" "type": "stable|transient" }
      • AND each status object in the reply shows as follows:
        "status": { "name": "deleting|error|running|scaling|starting|stopped|stopping" "type": "stable|transient" }

Developer Notes:
Stable means that no matter how long the service is left on its own that its state won't change. For example, if a service is in the running state, it will always stay in that state until told otherwise. Thus, it is stable. If a service is in the stopping state, it will only stay in that state for a while until it shuts down and ends up in the stopped state. Thus, the stopping state is transient.

Name Type
Deleting Transient
Error Stable
Running Stable
Scaling Transient
Starting Transient
Stopped Stable
Stopping Transient

Transition from Environment Variables to Azure Key Vault

Much of this application's configuration is set up via environment variables. However, some of these variables hold very sensitive information and shouldn't be sitting unencrypted in an environment variable. This story is to implement Azure Key Vault integration, remove the sensitive information from env vars, and move those values there.

In particular, here are the values that should be moved there:

  • LIVE_STREAMING_API_CLIENT_ID
  • LIVE_STREAMING_API_CLIENT_SECRET
  • LIVE_STREAMING_API_RESOURCE_GROUP
  • LIVE_STREAMING_API_SUBSCRIPTION_ID
  • LIVE_STREAMING_API_TENANT_ID

Helpful resources:

Remove Webhooks and Transition to the Event Grid

Webhooks really don't represent the best way of relaying information from one service to another. For this, Azure offers the Event Grid, which allows an event to leverage Azure's full suite of tools.

For this effort, all four of the webhooks should transition to Event Grid output bindings. These events should be triggered under the same conditions and should contain the same pieces of information that the existing webhooks send.

Helpful resources:

Create a New Project

Completely delete and re-create the Live Streaming API project. This includes the old project, solution, and README file.

As before, this must be a Visual Studio project, running on Azure Functions with a C# runtime targeting .NET 6 (LTS).

Here are the parameters to follow:

  • Azure functions project
  • .NET 6 (LTS)
  • C# runtime
  • Solution name: live-streaming-api
  • Project name: api
  • Namespace: LiteralLifeChurch.LiveStreamingApi
  • Author Name: Literal Life Church
  • Email: [email protected]
  • Company: Literal Life Church
  • Copyright: Copyright © 2023
  • Trademark: [Empty]
  • Version: 2.0.0
  • Description: A minimal API wrapper running on Azure Functions to create, publish, and read the status of one or more live broadcasts running on YouTube Live
  • Update all NuGet packages

You must also create a basic function. Create a function with these parameters:

  • HTTP trigger, with function-level authorization
  • Name: CreateBroadcast
  • Namespace: LiteralLifeChurch.LiveStreamingApi

Leave the default implementation the same

Enums Being Encoded by Ordinal, Not Name

All values that are encoded as enums in C# are being printed in JSON by the ordinal value, and not their name. For example, here is a GET call to the broadcaster's endpoint:

{"LiveEvents":[{"Name":"video","Status":{"Name":5,"Type":0}}],"StreamingEndpoint":{"Name":"default","Status":{"Name":5,"Type":0}},"Summary":{"Name":5,"Type":0}}

name and type should resolve to stopped and stable, respectively.

Build the Project with Azure Pipelines

To keep tabs on the quality of this project, we should have a CI pipeline to continuously build it. Let's use Azure Pipelines to do that for us.

For this issue, don't do anything fancy regarding CD or branch-specific tasks, just create a CI pipeline that runs the same task on all branches and PRs.

  • Ensure the application builds successfully
  • Add the appropriate badge to the README

Change Endpoints to Standard REST Conventions

As a user, I need the endpoints to use proper REST conventions so that can build an application on top of this following standard practices.

Acceptance Criteria:

  • GIVEN a GET request to /api/v1/broadcaster/status

    • WHEN the call is made
      • THEN the request is changed to /api/v1/broadcaster
      • AND the HTTP verb is GET
      • AND the contract and all other functionality remain the same
  • GIVEN a POST request to /api/v1/broadcaster/start

    • WHEN the call is made
      • THEN the request is changed to /api/v1/broadcaster
      • AND the HTTP verb is POST
      • AND the contract and all other functionality remain the same
  • GIVEN a DEL request to /api/v1/broadcaster/stop

    • WHEN the call is made
      • THEN the request is changed to /api/v1/broadcaster
      • AND the HTTP verb is DELETE
      • AND the contract and all other functionality remain the same

Developer Notes:
Don't forget to update the README and Postman documentation.

Start a Live Stream

As a user, I need to start the Azure Media services so that I can perform a live stream.

Acceptance Criteria:

  • GIVEN a request to start the live streaming services
    • WHEN not provided with the name of at least one or more live events (comma separated)
      • THEN throw a 400 Bad Request error
      • AND create a JSON object, as follows:
        {
            "message": "Input requires the name of one or more live events"
        }
        
    • WHEN not provided with the name of the streaming endpoint
      • THEN throw a 400 Bad Request error
      • AND create a JSON object, as follows:
        {
            "message": "Input requires the name of a streaming endpoint"
        }
        
    • WHEN not provided with the name of the streaming endpoint and the name of at least one or more live events (comma separated)
      • THEN throw a 400 Bad Request error
      • AND create a JSON object, as follows:
        {
            "message": "Input requires the name of a streaming endpoint and the name of one or more live events"
        }
        
    • WHEN provided with the name of one or more live events (comma separated) and a streaming endpoint to start
      • THEN the streaming endpoint is started, if necessary
      • THEN the requested live event(s) is started
    • WHEN creating the necessary services
      • THEN a live output is created for each of the given live events
      • THEN an asset is created for each live output
      • THEN a locator is created for each asset
    • WHEN associating the services together
      • THEN the asset is associated with the live output
      • THEN the asset is associated with the locator
    • WHEN the associations are finished
      • THEN start the live output
      • AND return a 201 Created response
      • AND create a JSON object, as follows:
        {
            "message": "Created"
        }
        
    • WHEN encountering an error with this process
      • THEN throw a 500 Internal Server error
      • AND create a JSON object, as follows:
        {
            "message": "An internal error occurred. Check the logs."
        }
        

Developer Notes:
The point of this step is not to wait everything out until things are fully created. That is the job of the forthcoming /status endpoint. This is just to create and start the services. Thus, sending a 200 OK response at the end of the whole pipe simply means the job is finished, but not that all of the Azure services are ready.

Logging will occur in a future story.

When creating each of the necessary services, stick to this naming convention:

  • Live Output: LiveStreamingApi-LiveOutput-<live event name>-<guid>
  • Asset: LiveStreamingApi-Asset-<live event name>-<guid>
  • Locator: LiveStreamingApi-Locator-<live event name>-<guid>

List the Locators

As a user, I need access to the locators for each live event so that I can view the live stream.

Acceptance Criteria:

  • GIVEN a request to list the locators for one or more live events
    • WHEN a request is made
      • THEN the endpoint is /locators
      • AND the access type is anonymous
      • AND the live events are passed in as a query parameter called events=event1,event2
    • WHEN not provided with the name of at least one or more live events (comma separated)
      • THEN throw a 400 Bad Request error
      • AND create a JSON object, as follows:
        {
            "message": "Input requires the name of one or more live events"
        }
        
    • WHEN provided with the name of one or more live events (comma separated) for which to supply a locator
      • THEN return 200 OK
      • AND the response contains the name of the requested event(s)
      • AND if at least one of the live events has a locator, then show isLive as true
      • AND if none of live events has a locator, then show isLive as false, and show liveEvents as an empty array
      • AND each event has a locator for HLS, DASH, and Smooth Streaming
      • AND create a JSON response, as follows:
        {
              "isLive": true|false
              "liveEvents": [{
                  "name": "<event name>",
                  "locators": [{
                      "type": "dash",
                      "url": "<url>"
                  }, {
                      "type": "hls",
                      "url": "<url>"
                  }, {
                      "type": "smooth",
                      "url": "<url>"
                  }]
              }, {
                  // Another object for each live event
              }]
        }
        
    • WHEN encountering an error with this process
      • THEN throw a 500 Internal Server error
      • AND create a JSON object, as follows:
        {
            "message": "An internal error occured during. Check the logs."
        }
        

Create a Durable Functions Start Operation

Starting an endpoint and live event that is connected to a CDN can take a very long time. In many cases, this process can range between 20 - 45 minutes to complete. That is simply too long to not return a reply.

Azure Durable Functions offers a convenient solution. It allows a user to start a long-running operation asynchronously, with a URL to check for continued status updates. That means that a user will trigger an operation, and get an immediate reply with a 202 Accepted header and URL to check at their leisure to see when the operation has been completed. The operation will continue to run in the background until it is completed.

This story is to create a Durable Functions implementation of the start operation, without removing support for the older one. Thus, the application will still support both synchronous and asynchronous variants of this endpoint, simultaneously.

Transition off the Old Functions Runtime

Microsoft has made some changes as to how their Azure functions SDK works. The old runtime uses Microsoft.Azure.Functions.* packages, while the new one uses Microsoft.NET.Sdk.Functions. Migrating from the old interface to the new one requires a few changes to the types that are used on each function.

This task should:

  • Migrate from Microsoft.Azure.Functions.* to Microsoft.NET.Sdk.Functions
  • All existing functions should continue to work, as before
  • Reinstate support for Azure Application Insights

Create Stable, Direct-Access URLs to the Manifests

Right now, the API provides information to a custom application and isn't useful in a broader scope. This effort is designed to smooth over these rough edges and provide a simple, memorable, and stable URL to a given manifest.

This would be helpful to allow users to use other applications, such as VLC Player, to stream the content from this API without needing to discover the custom manifest URL and update it each time the event reloads.

Here is the intended URL structure:

https://example.com/watch(/endpoint-name/event-name)?(/smooth|/dash|/hls|/)?

Whenever the optional parameters are not specified, here is what should happen:

  • Missing endpoint name: refer to the LIVE_STREAMING_API_DEFAULT_ENDPOINT_NAME variable
  • Missing event name: refer to the LIVE_STREAMING_API_DEFAULT_EVENT_NAME variable
  • Missing streaming protocol: smooth

Concrete examples with meaning:

Protocol mapping:
Azure supports many kinds of streaming protocols. Some of them have been simplified to match what Azure recommends without needing the know the exact name. Here is the mapping:

All other non-shorthand protocol names will pass the given name straight through to Azure. For example, m3u8-aapl-v3 is a valid protocol and will be placed on the end of the manifest request. No validation is done on this request. If the user places an invalid longhand protocol in the request like m3u8-aapl-solame, Azure will return HTTP 415, and so should this endpoint.

Note 1: These endpoints do NOT support more than one event name at a time, unlike the locators API, which can return many events. That would not make any sense in this scenario, since most players are only able to view one stream at a time.
Note 2: Missing or offline events or endpoints should result in a 404 error. Invalid protocol requests should result in a 415 error.

Create the Project

As a user, I need an API interface, so that I can control my live streaming events.

Acceptance Criteria:

  • GIVEN a project does not exist

    • WHEN creating a new project
      • THEN an Azure Functions project is created
      • AND the solution name is live-streaming-api
      • AND the project name is api
      • AND the template is HTTP Trigger
      • AND the access rights is Function
      • AND the target framework is .NET Standard 2.0
  • GIVEN the project is created

    • WHEN configuring the new project
      • THEN the solution & project version is 1.0.0 (project inherits from the solution)
      • AND the solution & project description is “A minimal API wrapper running on Azure Functions to enable, disable, and read the status of one or more live streaming sessions with Azure Media Services”
      • AND the author name is Oliver Spryn
      • AND the author email is [email protected]
      • AND the copyright is Copyright © 2020
      • AND the company is Literal Life Church
      • AND the default namespace is LiteralLifeChurch.LiveStreamingApi

Developer Notes: Make sure the README is updated accordingly.

Add Continuous Delivery to Azure Functions

The CI pipeline should be amended to include the ability to deploy to production whenever a merge to master happens. All other branches should continue to build, but should skip the deployment step.

Transition from Environment Variables to appsettings.json

Microsoft recommends using appsettings.json over environment variables for each Azure Functions instance. There are some distinct advantages that appsettings.json offers over environment variables. Thus, it is worth moving all of the environmental variables that are not affected by #62.

Here is a list of variables to move:

  • LIVE_STREAMING_API_ACCOUNT_NAME
  • LIVE_STREAMING_API_ARCHIVE_WINDOW_LENGTH

Keep in mind that these variables will need to remain for other services that this app uses:

  • APPINSIGHTS_INSTRUMENTATIONKEY: Per: https://stackoverflow.com/q/50717398/
  • AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, AZURE_TENANT_ID, AZURE_SUBSCRIPTION_ID: Added in #62
  • SENTRY_*: Being removed in #64
  • LIVE_STREAMING_API_WEBHOOK_*: Being removed in #65

Helpful resources:

Stylize README

Since some of the organization's README styling standards have changed, update this repository's header and footer styles to match the going theme.

Standardize the Stop Endpoint

As a user, I need a robust way to validate the information coming into the /stop endpoint and more information in the reply to report on the status of the request so that I can better understand what went on during a stop operation.

Acceptance Criteria:

  • GIVEN a request to stop one or more live events and the live streaming endpoint
    • WHEN making the request
      • THEN the endpoint does not change
      • AND the query parameters do not change
    • WHEN not provided with the name of the streaming endpoint
      • THEN throw a 400 Bad Request error
      • AND stop further processing
      • AND create a JSON object, as follows:
        {
            "status": 400,
            "type": "InputValidationException",
            "message": "Input requires the name of a streaming endpoint",
            "developerMessage": "The query parameter 'endpoint' is required and must be the name of an existing streaming endpoint"
        }
        
    • WHEN not provided with the name of at least one or more live events (comma separated)
      • THEN throw a 400 Bad Request error
      • AND stop further processing
      • AND create a JSON object, as follows:
        {
            "status": 400,
            "type": "InputValidationException",
            "message": "Input requires the name of one or more live events",
            "developerMessage": "The query parameter 'events' is required and must be a comma-separated list of names of existing live events"
        }
        
    • WHEN not provided with the name of the streaming endpoint and the name of at least one or more live events (comma separated)
      • THEN throw a 400 Bad Request error
      • AND stop further processing
      • AND create a JSON object, as follows:
        {
            "status": 400,
            "type": "InputValidationException",
            "message": "Input requires the name of a streaming endpoint and the name of one or more live events",
            "developerMessage": "The query parameter 'endpoint' is required and must be the name of an existing streaming endpoint, and another query parameter 'events' is also required and must be a comma-separated list of names of existing live events"
        }
        
    • WHEN given a streaming endpoint, but it does not exist
      • THEN throw a 400 Bad Request error
      • AND stop further processing
      • AND create a JSON object, as follows:
        {
            "status": 400,
            "type": "ServiceValidationException",
            "message": "The given streaming endpoint does not exist",
            "developerMessage": "The query parameter 'endpoint' does not match the name of any existing streaming endpoints"
        }
        
    • WHEN given at least one live event but at least one of those events does not exist
      • THEN throw a 400 Bad Request error
      • AND stop further processing
      • AND create a JSON object, as follows:
        {
            "status": 400,
            "type": "ServiceValidationException",
            "message": "The following live event(s) do not exist: <comma separated list of invalid events>",
            "developerMessage": "The following live event(s) in the query parameter 'events' does not match the name(s) of any the existing live events: <comma separated list of invalid events>"
        }
        
    • WHEN given a streaming endpoint and at least one live event and all of those resources exist
      • THEN it stops the given streaming endpoint and all live events, as before
    • WHEN a streaming endpoint is not in a running state
      • THEN it skips stopping the endpoint
    • WHEN any given live event is not in a running state
      • THEN it skips stopping that endpoint, but attempts to stop all the rest, if the above criteria is satsified
    • WHEN the operation completes
      • THEN return a 200 OK response
      • AND return the status of the endpoint and all given live events, just like the /status endpoint
      • AND return the diff between the pre-stop service statuses and the post-stop statuses, altogether, like this:
        {
            "changes": {
                "endpoint": {
                    "name": "<name of endpoint>",
                    "newStatus": "<status after stop request>",
                    "oldStatus": "<status before stop request>"
                },
                "events": [{
                    "name": "<name of event>",
                    "newStatus": "<status after stop request>",
                    "oldStatus": "<status before stop request>"
                 }, {
                    // Additional events here
                 }]
            },
            "status": {
                "summary": "<correct status>",
                "endpoint": {
                    "name": "<name of endpoint>",
                    "status": "<correct status>"
                },
                "events": [{
                    "name": "<name of event>",
                    "status": "<correct status>"
                }, {
                    // Additional events here
                }]
            }
        }
        

Developer Notes:
The resulting status may not be equal to stopped at the end of a stop operation request. If for any reason, any existing endpoint or live event is not in the running state, they remain unchanged. For example, if one live event was in the process of starting when the stop request happened, that live event will not be changed by the application and will result in a starting status, as defined by #13.

Upgrade from .NET Core 2.0 to .NET 5

This project has not been updated since .NET Core 3.0 or .NET 5 were introduced. Since .NET Core 2.0 was not an LTS version and is now EoL as of December 2019, this project must be upgraded. In this development task:

  • Upgrade to the latest version of .NET 5
  • Upgrade all of the NuGet libraries
  • Make any necessary changes to the code to get it functional, as it was before

Note: This upgrade removes the need for initializing Application Insights telemetry support. That support can be added via the Management Console for this functions instance.

Obtain YouTube API Authorization

The very first step in this entire project is to gain authorization for a YouTube channel. This can be done via OAuth 2.0 for Web Server Applications.

Use a dummy YouTube channel for this purpose. The end result should be the application having an access and refresh token for the proper scopes:

Here are some helpful pages:

Standardize the Locators Endpoint

As a user, I need a robust way to validate the information coming into the /locators endpoint and more information in the reply to show the mapping between live events and their manifest URLs so that I can better understand how to interpret that I am given.

Acceptance Criteria:

  • GIVEN a request for the manifest URLs for one or more live events and the live streaming endpoint
    • WHEN making the request
      • THEN the endpoint does not change
      • AND the query parameters do not change
    • WHEN not provided with the name of the streaming endpoint
      • THEN throw a 400 Bad Request error
      • AND stop further processing
      • AND create a JSON object, as follows:
        {
            "status": 400,
            "type": "InputValidationException",
            "message": "Input requires the name of a streaming endpoint",
            "developerMessage": "The query parameter 'endpoint' is required and must be the name of an existing streaming endpoint"
        }
        
    • WHEN not provided with the name of at least one or more live events (comma separated)
      • THEN throw a 400 Bad Request error
      • AND stop further processing
      • AND create a JSON object, as follows:
        {
            "status": 400,
            "type": "InputValidationException",
            "message": "Input requires the name of one or more live events",
            "developerMessage": "The query parameter 'events' is required and must be a comma-separated list of names of existing live events"
        }
        
    • WHEN not provided with the name of the streaming endpoint and the name of at least one or more live events (comma separated)
      • THEN throw a 400 Bad Request error
      • AND stop further processing
      • AND create a JSON object, as follows:
        {
            "status": 400,
            "type": "InputValidationException",
            "message": "Input requires the name of a streaming endpoint and the name of one or more live events",
            "developerMessage": "The query parameter 'endpoint' is required and must be the name of an existing streaming endpoint, and another query parameter 'events' is also required and must be a comma-separated list of names of existing live events"
        }
        
    • WHEN given a streaming endpoint, but it does not exist
      • THEN throw a 400 Bad Request error
      • AND stop further processing
      • AND create a JSON object, as follows:
        {
            "status": 400,
            "type": "ServiceValidationException",
            "message": "The given streaming endpoint does not exist",
            "developerMessage": "The query parameter 'endpoint' does not match the name of any existing streaming endpoints"
        }
        
    • WHEN given at least one live event but at least one of those events does not exist
      • THEN throw a 400 Bad Request error
      • AND stop further processing
      • AND create a JSON object, as follows:
        {
            "status": 400,
            "type": "ServiceValidationException",
            "message": "The following live event(s) do not exist: <comma separated list of invalid events>",
            "developerMessage": "The following live event(s) in the query parameter 'events' does not match the name(s) of any the existing live events: <comma separated list of invalid events>"
        }
        
    • WHEN given a streaming endpoint and at least one live event and all of those resources exist
      • THEN it retrieves the DASH, HLS, and Smooth streaming manifest URLs for each of the requested live events
      • AND it provides a summary of its findings, like so:
        {
            "events": [{
                 "name": "<name of event>",
                 "isLive": <true if the event is on-air, false otherwise>,
                 "locators": [{
                    "type": "dash",
                    "url": "<URL>"
                 }, {
                    "type": "hls",
                    "url": "<url>"
                 }, {
                    "type": "smooth",
                    "url": "<url>"
                 }]
            }, {
               "name": "<name of event>",
               "isLive": <true if the event is on-air, false otherwise>,
               "locators": <empty when no URLs are available>
            }, {
               // Additional events here
            }],
            "isAllLive": <true when all requested live events are on-air, false otherwise>,
            "isAnyLive": <true when any requested live event is on-air, false otherwise>
        }
        

Update Paths and HTTP Verbs

As a user, I need to follow proper REST conventions so that I can build front-end applications that follow standard practices.

Acceptance Criteria:

  • GIVEN any endpoint in the application
    • THEN all of the existing paths are prefixed with api/v1, such as https://website.com/api/v1/locators
  • GIVEN the /start endpoint
    • WHEN making a call to that endpoint
      • THEN the call must be a POST request
      • AND the path is changed to /broadcaster/start so the full path reads /api/v1/broadcaster/start
  • GIVEN the /stop endpoint
    • WHEN making a call to that endpoint
      • THEN the call must be a POST request
      • AND the path is changed to /broadcaster/stop so the full path reads /api/v1/broadcaster/stop
  • GIVEN the /status endpoint
    • WHEN making a call to that endpoint
      • THEN the call must be a GET request
      • AND the path is changed to /broadcaster/status so the full path reads /api/v1/broadcaster/status
  • GIVEN the /locators endpoint
    • WHEN making a call to that endpoint
      • THEN the call must be a GET request
      • AND the path is changed to /viewer/locator so the full path reads /api/v1/viewer/locator

Add Support for Webhooks

As a user, I need to know when requests are made to start and stop the Azure resources so that I can be better informed about the status of these resources and the charges they incur.

Acceptance Criteria:

  • GIVEN a request to start the streaming endpoint
    • WHEN the services start successfully
      • THEN a POST call is made to a webhook indicating that the start operation completed successfully
      • AND the webhook has a query parameter of action=start
      • AND the webhook has a query parameter of status=<summary value from the call to status service>
      • AND the body of the request is encoded as application/json
      • AND the body of the request is:
        {
            "action": "start",
            "status": <summary value from the call to status service>
        }
        
    • WHEN the services do not start successfully
      • THEN a POST call is made to a webhook indicating that the start operation failed
      • AND the webhook has a query parameter of action=start
      • AND the webhook has a query parameter of status=error
      • AND the body of the request is encoded as application/json
      • AND the body of the request is:
        {
            "action": "start",
            "status": "error"
        }
        
    • WHEN the services stop successfully
      • THEN a POST call is made to a webhook indicating that the stop operation completed successfully
      • AND the webhook has a query parameter of action=stop
      • AND the webhook has a query parameter of status=<summary value from the call to status service>
      • AND the body of the request is encoded as application/json
      • AND the body of the request is:
        {
            "action": "stop",
            "status": <summary value from the call to status service>
        }
        
    • WHEN the services do not stop successfully
      • THEN a POST call is made to a webhook indicating that the stop operation failed
      • AND the webhook has a query parameter of action=stop
      • AND the webhook has a query parameter of status=error
      • AND the body of the request is encoded as application/json
      • AND the body of the request is:
        {
            "action": "stop",
            "status": "error"
        }
        
  • GIVEN an administrator who is setting up the application on an Azure Functions instance
    • THEN there is an environment variable called LIVE_STREAMING_API_WEBHOOK_START_SUCCESS which is a URL called when the resources successfully start
    • THEN there is an environment variable called LIVE_STREAMING_API_WEBHOOK_START_FAILURE which is a URL called when the resources do not successfully start
    • THEN there is an environment variable called LIVE_STREAMING_API_WEBHOOK_STOP_SUCCESS which is a URL called when the resources successfully stop
    • THEN there is an environment variable called LIVE_STREAMING_API_WEBHOOK_STOP_FAILURE which is a URL called when the resources do not successfully stop

Developer Notes:
A success condition only means that no exceptions were thrown during the operation. If the operation does not reach its intended state (e.g. a /stop request does not stop everything because one of the resources was still trying to start up when the /stop request occurred), then the status query parameter will report on any discrepancies and leave the user to decide what to do.

Add Support for Adjustable Archive Window

As a user, I need to be able to adjust the archive window buffer size, so that my event viewers can rewind a live event back a given amount of time.

Acceptance Criteria:

  • GIVEN the application installation
    • WHEN setting up the environment variables
      • THEN there is a variable called LIVE_STREAMING_API_ARCHIVE_WINDOW_LENGTH where the accepted value is an integer indicating the maximum number of minutes that a user can rewind the live event back into the past

Authenticate with Azure Media Services

As a user, I need to authenticate with Azure Media Services so that I can control the channels and programs running on it.

Acceptance Criteria:

  • GIVEN the user makes a request to the application
    • WHEN the application begins communicating with Azure
      • THEN it authenticates itself with Azure before making further requests

Developer Notes:

  • Consider making a simple, read-only request after authenticating to ensure it works

Build Project with GitHub Actions

#54 had to remove support for Azure Pipelines since the runners did not support .NET 6. This task is to add back CI for this project via updated GitHub Actions runners.

Set Version to 1.0.0

Just prior to release, the application's assembly information should be changed to version 1.0.0.

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.