class Application < Lively::Application
- Inherits from
Lively::Application
Represents the main Presently application middleware.
Routes the presentation, recording, playback, export, and live interfaces.
Creates a shared class Presently::PresentationController that keeps connected clients in sync.
Definitions
def initialize(delegate, slides_root: "slides", templates_roots: [], recordings_root: nil, playback_recordings_root: nil)
Initialize a new Presently application.
Signature
-
parameter
delegateProtocol::HTTP::Middleware The next middleware in the chain.
-
parameter
slides_rootString The directory containing slide files.
-
parameter
templates_rootsArray(String) Additional directories to search for templates.
-
parameter
recordings_rootString | Nil Directory where source narration is stored. Defaults to
audiobeside the slides directory.-
parameter
playback_recordings_rootString | Nil Directory containing normalized narration. Defaults to
audio-normalizedbeside the slides directory.
Implementation
def initialize(delegate, slides_root: "slides", templates_roots: [], recordings_root: nil, playback_recordings_root: nil)
@slides_root = slides_root
@templates_roots = templates_roots
@recordings = Recordings.new(recordings_root || File.expand_path("../audio", slides_root))
@playback_recordings = Recordings.new(playback_recordings_root || File.expand_path("../audio-normalized", slides_root))
slide_assets = SlideAssets.new(delegate, root: @slides_root, stylesheets: ->{controller.presentation.stylesheets})
super(slide_assets)
end
def allowed_views
The view classes that this application allows.
Signature
-
returns
Array(Class) The allowed view classes.
Implementation
def allowed_views
[HomeView, DisplayView, PresenterView, RecordingView]
end
def state
The shared state passed to all views via the resolver.
Signature
-
returns
Hash The controller as keyword state.
Implementation
def state
{controller: controller}
end
def controller
The shared presentation controller.
Signature
-
returns
PresentationController The controller instance.
Implementation
def controller
@controller ||= begin
templates = Templates.for(@templates_roots)
presentation = Presentation.load(@slides_root, templates)
PresentationController.new(presentation, state: State.new)
end
end
def title
The application title shown in the browser.
Signature
-
returns
String The page title.
Implementation
def title
"Presently"
end
def make_page(view)
Create a Presently page with the presentation-specific stylesheets.
Signature
-
parameter
viewLive::View The root view for the page.
-
returns
Page The presentation page.
Implementation
def make_page(view)
stylesheets = controller.presentation.stylesheets.map(&:url)
Page.new(title: title, body: view, stylesheets: stylesheets)
end
def configure_routes(router)
Add Presently's application routes.
Signature
-
parameter
routerLively::Router::Builder The router to configure.
Implementation
def configure_routes(router)
router.get("/") do
body = resolver.root(HomeView)
Page.new(title: title, body: body).call
end
router.get("/display"){make_page(resolver.root(DisplayView)).call}
router.get("/presenter"){make_page(resolver.root(PresenterView)).call}
router.get("/record"){make_page(resolver.root(RecordingView)).call}
router.route("/recordings", methods: ["GET", "HEAD", "PUT"]) do |request|
handle_recording(request, request_parameters(request))
end
router.route("/playback/recordings", methods: ["GET", "HEAD"]) do |request|
handle_playback_recording(request, request_parameters(request))
end
router.get("/playback") do |request|
render_playback(request_parameters(request))
end
router.get("/export") do |request|
render_export(request_parameters(request))
end
end
def request_parameters(request)
Parse query parameters from the incoming request target.
Signature
-
parameter
requestProtocol::HTTP::Request The incoming request.
-
returns
Hash The decoded query parameters.
Implementation
def request_parameters(request)
Protocol::URL::Reference[request.path].parse_query!
end
def render_playback(parameters)
Render the narrated playback interface.
Implementation
def render_playback(parameters)
presentation = Presentation.load(@slides_root, controller.templates)
recording_urls = presentation.slides.each_index.map do |index|
slide = presentation.slides[index]
if @playback_recordings.exist?(slide) || @recordings.exist?(slide)
"/playback/recordings?index=#{index}"
end
end
playback = Playback.new(
presentation: presentation,
recording_urls: recording_urls,
**Playback.options_from_parameters(parameters),
)
Protocol::HTTP::Response[200, [["content-type", "text/html"]], [playback.call]]
end
def render_export(parameters)
Render the printable export interface.
Implementation
def render_export(parameters)
presentation = Presentation.load(@slides_root, controller.templates)
export = Export.new(presentation: presentation, **Export.options_from_parameters(parameters))
Protocol::HTTP::Response[200, [["content-type", "text/html"]], [export.call]]
end
def handle_recording(request, parameters)
Handle reading and writing a slide recording.
Signature
-
parameter
requestProtocol::HTTP::Request The incoming request.
-
parameter
parametersHash The decoded query parameters.
-
returns
Protocol::HTTP::Response
Implementation
def handle_recording(request, parameters)
index = recording_index(parameters)
return Protocol::HTTP::Response[400, [], ["A valid slide index is required."]] unless index
slide = controller.slides[index]
return Protocol::HTTP::Response[404, [], ["Slide not found."]] unless slide
case request.method
when "GET", "HEAD"
serve_recording(request, slide, @recordings)
when "PUT"
store_recording(request, slide)
end
end
def handle_playback_recording(request, parameters)
Serve normalized narration for playback, falling back to the source take.
Implementation
def handle_playback_recording(request, parameters)
index = recording_index(parameters)
return Protocol::HTTP::Response[400, [], ["A valid slide index is required."]] unless index
slide = controller.slides[index]
return Protocol::HTTP::Response[404, [], ["Slide not found."]] unless slide
recordings = @playback_recordings.exist?(slide) ? @playback_recordings : @recordings
serve_recording(request, slide, recordings)
end
def recording_index(parameters)
Extract the slide index from decoded query parameters.
Signature
-
parameter
parametersHash The decoded query parameters.
-
returns
Integer | Nil
Implementation
def recording_index(parameters)
Integer(parameters.fetch("index"))
rescue ArgumentError, KeyError
nil
end
def serve_recording(request, slide, recordings)
Serve an existing recording.
Signature
-
parameter
requestProtocol::HTTP::Request The incoming request.
-
parameter
slideSlide The requested slide.
-
returns
Protocol::HTTP::Response
Implementation
def serve_recording(request, slide, recordings)
unless recordings.exist?(slide)
return Protocol::HTTP::Response[404, [], ["Recording not found."]]
end
headers = [
["content-type", Recordings::CONTENT_TYPE],
["cache-control", "no-store"],
]
body = recordings.read(slide) unless request.method == "HEAD"
Protocol::HTTP::Response[200, headers, body]
end
def store_recording(request, slide)
Store an uploaded recording.
Signature
-
parameter
requestProtocol::HTTP::Request The incoming request.
-
parameter
slideSlide The slide being recorded.
-
returns
Protocol::HTTP::Response
Implementation
def store_recording(request, slide)
content_type = request.headers["content-type"]&.split(";", 2)&.first
unless content_type == Recordings::CONTENT_TYPE
return Protocol::HTTP::Response[415, [], ["Expected #{Recordings::CONTENT_TYPE}."]]
end
unless request.body
return Protocol::HTTP::Response[400, [], ["A recording body is required."]]
end
@recordings.write(slide, request.body)
Protocol::HTTP::Response[201, [["content-type", "application/json"]], ["{\"saved\":true}"]]
rescue Recordings::TooLarge => error
Protocol::HTTP::Response[413, [], [error.message]]
end