Start of Trimester 2
Recap of Trimester 1 and MCQ
Start of Trimester 2 AP CSA
My Project
Our class last trimester split up into various themes for projects, one side related to fun and games, the other related to making useful tools for our teacher, John Mortenson. Our projects theme was developing tools, and on our ProductivityFrontend branch, our team’s project was…
Mortevision
One aspect of our lives in AP CSA is doing presentations on our work.
In that process there is often a lot of clutter in who has presented; cramming around one of three televisions; and it being an overall unclean process.
That is why my team, JITS, developed Mortevision.
Our team is comprised of Ian Wu, Srijan Atti, Kayden Le, Tarun Jaikumar, and me. Mortevision is a tool designed to help with this process, and my task was to develop the presenting system, or screen share. One thing I’ve always disliked was having to cram around 12 other people to look at one tv screen, when I could be at my desk and just watch the stream from there.
So that’s what I developed, a livestream system using WebRTC to allow others to watch a stream from their desks and allow for easy connections with no third party like Ditto required.
The source code is as follows
let peerConnection
async function startStream()
{
let stream
try
{
stream = await captureScreen()
}
catch
{
console.error("can u js be normal man")
}
if (!peerConnection) {
peerConnection = createPeerConnection();
}
stream.getTracks().forEach((track) => peerConnection.addTrack(track, stream)); //add media stream to each connected peer
}
async function viewStream()
{
if(!peerConnection)
{
peerConnection = createPeerConnection()
}
if(peerConnection.getTransceivers().length === 0)
{
peerConnection.addTransceiver("video", { direction: "recvonly" }) //recieve only
peerConnection.addTransceiver("audio", { direction: "recvonly" })
document.getElementById("streamOffline").style.display = "none"
document.getElementById("mortStream").style.display = "block"
}
await peerNegotiation()
}
async function captureScreen() {
let mediaStream = null;
try {
mediaStream = await navigator.mediaDevices.getDisplayMedia({
video: {
cursor: "always"
},
audio: true
}); //get user video and audio as a media stream
document.getElementById("streamOffline").style.display = "none"
document.getElementById("mortStream").style.display = "block"
document.getElementById("mortStream").srcObject = mediaStream
return mediaStream;
} catch (ex) {
console.log("Error occurred", ex);
}
}
function createPeerConnection()
{
const peer = new RTCPeerConnection(servers)
peer.addTransceiver('video', {
direction: 'recvonly'
});
peer.addTransceiver('audio', {
direction: 'recvonly'
});
peer.onnegotiationneeded = () => peerNegotiation()
peer.onicecandidate = ({ candidate }) => {
if (candidate && signalingServer.readyState === signalingServer.OPEN) {
signalingServer.send(JSON.stringify({ target: id, type: 'candidate', payload: candidate }));
}
};
peer.ontrack = ({streams}) => {
document.getElementById("mortStream").srcObject = streams[0]
document.getElementById("mortStream").style.display = "block"
document.getElementById("streamOffline").style.display = "none"
}
return peer
}
async function peerNegotiation()
{
if(!peerConnection)
{
peerConnection = createPeerConnection()
}
const offer = await peerConnection.createOffer();
await peerConnection.setLocalDescription(offer);
signalingServer.send(JSON.stringify({ target: id, type: 'offer', payload: offer }));
}
let id
signalingServer.onmessage = async(message) => {
const data = JSON.parse(message.data)
console.log(data.type)
switch(data.type)
{
case "init":
id = data.id
break;
case "offer":
if (!peerConnection) {
peerConnection = createPeerConnection();
}
await peerConnection.setRemoteDescription(new RTCSessionDescription(data.payload)); // Ensure remote description is set
const answer = await peerConnection.createAnswer(); // Create answer only after setting remote description
await peerConnection.setLocalDescription(answer); // Set the local description
signalingServer.send(JSON.stringify({ target: data.payload.id, type: 'answer', payload: answer }));
// signalingServer.send(JSON.stringify({description: peerConnection.localDescription}));
break;
case "answer":
if (!peerConnection) {
peerConnection = createPeerConnection();
}
await peerConnection.setRemoteDescription(new RTCSessionDescription(data.payload));
break;
case "candidate":
if (!peerConnection) {
peerConnection = createPeerConnection();
}
await peerConnection.addIceCandidate(new RTCIceCandidate(data.payload));
break;
}
}
function toggleBroadcastButton(isVisible)
{
if(isVisible)
{
document.getElementById("broadcastButton").style.display = "flex"
return
}
else
{
document.getElementById("broadcastButton").style.display = "none"
}
}
There are a few other scripts but for the most part they do not matter.
Before I breakdown the code, let’s talk about how this works fundamentally.
A peer represents a client or a computer connected to the stream.
WebRTC creates a real time connection between these peers by directly sending the data (video and audio) between the two peers. In order to send these data streams, we need to actually know what peer or ip address to send it to. To do this each user will use a STUN server, which I used the ones publicly available by Google, to find their public IP address. Then once a user clicks the broadcast button, it sends a signal to the signaling server. Skipping some information, the signaling server is the Java backend in which the two users communicate with each other on signaling information such as someone joining the stream, starting a broadcast, and other events if necessary.
But what is the-
Signaling Server
The WebRTC connection needs a middle man to resolve how to connect them together. The signaling server serves this purpose by sending events between the two clients through the usage of WebSocket, which allows for real time events. These events are typically called when creating a peer negotation or peer connection.
Here’s a look into what this looks like on the backend itself.
Config
package com.nighthawk.spring_portfolio.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(new SignalingHandler(), "/socket")
.setAllowedOrigins("*");
System.out.println("why");
}
}
This script will create a /socket endpoint we can send requests to on our HTML front end given our ws:// local address For the sake of ease of testing, the allowed origins is set to all, but in production it would be set to the nighthawkcoders domain.
Signaling Handler
package com.nighthawk.spring_portfolio.config;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import java.util.ArrayList;
import java.util.List;
public class SignalingHandler extends TextWebSocketHandler {
private final List<WebSocketSession> sessions = new ArrayList<>();
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
sessions.add(session);
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
for (WebSocketSession peerSession : sessions) {
if (!peerSession.getId().equals(session.getId()) && peerSession.isOpen()) {
peerSession.sendMessage(message);
}
}
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
sessions.remove(session);
}
}
This simple script keeps tracks of what “sessions” there are, essentially representing each connected user. If a user joins they will be added to the ArrayList, and removed when the leave. The most important block of code is the handleTextMessage() method, as it iterates through the connected users and sends them information so long as they themselves are not the sender, such as joining or broadcasting.
Night At The Museum
Assignment | Points | Grade | Evidence |
---|---|---|---|
Sprint 1-3 Review Ticket | 3 | Sprint 1 Review Ticket Sprint 2 Review Ticket Sprint 3 Review Ticket Sprint 1 Bonus |
|
Sprint 3 Team Issue(s)/Plan | 2 | Team Plan | |
Beginning-2-End Contribution | 2 | Ideation To Do List |
|
N@tM Team Presentation | 2 | ||
Live Review Indi Demo | 1 | See Above | |
Total | 10 |
Skill | Points | Grade | Evidence |
---|---|---|---|
Work Habits (Analytics) | 1 | ||
Evidence of Role in Team | 1 | ||
Function / Purpose Design | 1 | ||
Live Review | 2 | ||
Total | 5 |
Collegeboard Practice MCQ
Score: 19/40
So it’s not the greatest score for practice, so going forward I’ll focus on getting things right more than getting things done…
Corrections
Q40 C) The recursive call isn’t printed before the whatsItDo(temp), so it is going forward printing lines
Q39 D) recur(9) is 18, the progression should have continued with recur(18) so the correct progression should have been recur(recur(27/3)) -> recur(18) -> recur(recur(6)) -> recur(12) -> recur(recur(4)) -> recur(8) -> 16
Q38 A) X is not a required variable if y is more than 10000, so A (y > 10000 | | x > 1000) && (y > 10000 | | x < 1500) is the right answer as it keeps x in the range with &&
Q37 D) x < 6 and x < 7 will work as previously I only chose x < 6; however, x < 7 will still terminate the loop at the same time
Q36 C) 8, 9, and 11 will work because 8 % 2 ==0, 9 %2 != 0 and 9 is not greater than 9, so the else case will fire, and 11 > 9
Q35 E) 4752 as for loop 1 result is 4, num is 257, loop 2 result 47 num 25, loop 3 result 475 num 2, loop 4 result 4752 num 0
Q34 B) II only being center = new Point(a,b); radius = r; because I will not assign the center and center itself is private
Q33 E) Infinite loop as k will always be < 4 because it never increases
Q30 C) ilercom as it is getting the 5th value to the end, before chaining the beginning to the start to the 4th value to the end
Q26
A) This will work because it is iterating through each value in the array and seeing if the number % 2 is not equal to 0, meaning it has to be odd
Q25 D) I and II as solution III does not prove it can fit as surface area and volume are not enough
Q24 D) 7 as the array contains { {1, 4, 7}, {2, 5, 8}, {3, 6, 9} }, and the 3rd value in the first column is 7
Q23 B) my initial mistake was I thought words that started with b were sent to the end as opposed to being inserted at animals.size() - k
Q22 B) Line 5 will compile because it inherits the superclass’s toString
Q21 D)Math.abs (row [num] - val) < minDiff correctly completes the code as my answer relies on it being a for loop as opposed to a for each loop
Q20 E) The numbers are flipped as j increases and k decreases
Q18 Assume that myList is an ArrayList that has been correctly constructed and populated with objects. Which of the following expressions produces a valid random index for myList? Responses B) (int) ( Math.random () * myList.size () ) is the correct answer as my original answer decreased the range by 1
Q16 D) My original answer once again decreases the range by 1
Q12 C) optr is the output as it gets every 2nd value and chains them together
Q11 B) The answer if(last < 0){return -1} works as if it is <= 0 then it will return -1 instead of 0>
Q10 B) II works as it is fine if the element only contains one element, but II will eventually cause an out of bounds exception