Categories
Uncategorized

Richard from Community Hill, ‘I’m glad that Fora Soft has designers, analytics, testers.’

Here’s an interview with Richard, the owner of Community Hill. Richard’s entrepreneurship has been going on for 10 years – since the times Amazon was selling books. Now he’s working with Fora Soft, which is his last hope.

What do you have to expect going into work with Fora Soft? What can we teach an experienced entrepreneur who had worked with many developers before coming to Fora Soft? The answers are in the interview.

I also tried my best to find out the problems Richard might’ve had with Fora Soft, so that the feedback is truly genuine. It turned out to be a difficult task, as everything has been spot on. Enjoy!


Nikita: What is Community Hill and who is it for? 

Richard: Thank you for this opportunity. Community Hill is an online platform with people of various communities, mainly the towns and the villages where people live. People sell various items, usually household items, including furniture, vehicles and others. Service providers post their services. For instance, electricians. So on that platform, people post everything, and if anyone is interested, contact them. They plan a time and location to meet and then they do the transaction. That’s what Community Hill is about. We’re building the community together, you know? 

Nikita: Cool. So you’ve come to Fora Soft. When did this happen? 

Richard: That was last year. Probably August or September? I went to an online auction platform first. I was looking for a team that could really make a good product. And then one of the team members of Fora Soft contacted me. He told me that we’d get in touch. I’m glad that we got it started. So I’ve been with you for 9-10 months. And I’m still with you because we have to do the 2nd phase. 

Nikita: That’s very good to hear. But before Fora Soft, was there any company that you were going to work with? Perhaps, Fora Soft wasn’t your first choice? 

Richard: Yes. At first I contacted one of the programmers. That platform where I went has so many programmers. You can never know whether someone really knows what he’s doing or whether he’s also outsourcing somewhere else. So the first person didn’t really do a good job at all. So I kind of lost hope in these individual freelancers. I decided that I needed a team of people that can really do something. And when I checked on that, all of a sudden Fora Soft showed up. Nothing else showed up, actually. I read the reviews on Fora Soft and it started from there. I didn’t look for anything else. So, that’s how we met.

Nikita:  You mentioned having the team in Fora Soft, a project team consists of developers, project managers, designers, QA… We’ve got marketing, promotion and so on and so forth. During the production of your app, did you ever feel that you really need all those people? Or was it like, “I don’t really understand why I need so many people, and I’m good with like 2 or 3 of them, not the whole team?” 

Richard: I needed all of them. Let me give you an example. For instance, designing a logo. I’m not an artist. I don’t even have the knowledge of how a good logo should look. So I just needed everyone to come on board because I didn’t know much about these things. I didn’t know how the best user stories should be. So I just needed a team of everyone to be on board.

And then when I was told that we even have testers, I was so glad that they had that team.

Nikita: Over the time of us working together, was there anything that you wish that Fora Soft would improve, maybe some downsides of working with us? 

Richard: I haven’t come across anything because Fora Soft is good at communicating. You keep in touch. I always communicate with the programmer, the project manager, the designer. Because of that constant communication, there hasn’t been a gap for doubt. Like every time I have an issue or an idea, I usually ask them and then we find a solution right away. We could also find a date to have a meeting and talk about it.

Nikita: Good to hear. Let’s talk a little bit about your app. So it’s an iOS app. Are you planning on going to Android or Web or something else? 

Richard: Oh, yeah, definitely. If we get more funding, I want us to finish this 2nd phase of iOS and then I also want us to do the Android version or the web version as well. 

Nikita: Okay. And Community Hill is currently an MVP, right? 

Richard: Correct. 

Nikita: During this MVP, did you have any people to test the app? Maybe yourself, your family, or friends. 

Richard: Yeah, sure. It’s been myself at the moment. I haven’t been able to ask any family members to test, so it’s just me and the programmer for now.

Nikita: I guess you did come across some bugs because, unfortunately, they’re imminent. Do you feel that there were lots of bugs or were they kept to a minimum? 

Richard: Well, at some point, as I was testing the app – the MVP –  it crashed. And then I mentioned it to the programmer, and he said that he was going to check it out to find a solution for it. Other than that, I didn’t see any other bugs on my side. Maybe on their side, they might have seen some because so many people were testing, so they could identify more issues. But on my side, I didn’t see anything else. 

Nikita: Right. And after the developer said that he was going to look into the problem, did he keep his promise? 

Richard: Yes, he told me that they have also identified other issues and they are going to work on them. And we are yet to have the meeting this week (the meeting went peachy, Fora Soft resolved everything). We’re gonna discuss the issues that they found. So I would say he kept his promise. 

Nikita: Is there something you want to add or say about Fora Soft that relate to our other clients?

Richard:

I would say that any anyone who comes Fora Soft should be really available for constant communication.

That’s what really helps to keep the project going. If one is really busy and not able to keep in touch, I think it will be so hard to keep the project moving. It’s all about communication. You’re on the same page with the programmers. Your expectations and their expectations are on the same level. I think that’s the most important thing – be ready to keep in touch with all the team members. Starting with that, with the analytic, then programmers, and all those people, it’s about communication. 

Nikita: Right. And actually, I have one more question that I kind of forgot to ask in the beginning. Richard, do you yourself have some kind of technical background? 

Richard: A little bit. I’ve always had the passion of online businesses. I started out venturing into online businesses like 10 years ago, those years when Amazon was just getting started, when they were just selling books. I had this idea of the market place, but I didn’t have the money. So I reached out to the local programmers and they really did do a good job. They didn’t do what I really wanted. So I’ve been trying out this field of online businesses for a long time. During that time, of course, I’ve come to learn a little bit of what programing is all about, what a programmer is and all that kind of stuff. I’ve developed a little bit of technical knowledge in that field. But I don’t really have that much technical background, just a little bit, only because I’ve been trying out this field for a very long time. I must say that Fora Soft is my last try. If it fails, I’m out of it because it’s been like 10 years. I’m trying this out since the time Amazon was selling books. I was building ideas on how to make a good marketplace. It’s been a long time. So I would say Fora Soft is my last try. If it fails, I let it go. 

Nikita: Well, I guess I can speak on behalf of Fora Soft, that we’ll do our best to satisfy your needs. And actually what I wanted to ask is that you barely have some technical background. You are not a developer by any means, as far as I understand. Did you ever feel that you are lacking the skills to produce Community Hill with Fora Soft? Or did it feel like you don’t actually need to understand the codes or anything like that? You can just give Fora Soft an assignment and expect a result, and you can give this assignment using some human language, not developer language. 

Richard: Yeah, I knew that I didn’t really need to be a developer, but I knew what final product should look like. Because, like I said, I’ve been trying out these things. I’ve seen how Amazon has been changing the way you interact with the app, the website, all those things that they do on their marketplaces. So I’ve kind of got the knowledge on how the best product should look like. So I said, I don’t really have to be a developer, but I know how a final product should look like. 

Nikita: Can you please rate Fora Soft on a scale of ten in terms of communication, professionalism, and determination in the project’s involvement, perhaps. 

Richard: Oh, it’s ten out of ten. The local developers and freelancers I interacted with before I came to Fora Soft, they didn’t even tell me about user stories.

I first heard about user stories from Fora Soft.

I’d never heard that word. The freelancers I reached out to before, they didn’t even know these things. Like they didn’t have the professional knowledge of how a project should run. They didn’t even give me a development plan. I first saw a development plan from Fora Soft. These freelancers didn’t have those things. They didn’t have that kind of communication. So I’m glad that I chose Fora Soft. The rating is 10 out of 10. 


So, that’s it! Wanna, just like Richard, enjoy all the best that Fora Soft has to offer? Make sure to contact us using the contact form!

Not convinced yet? Read more customer interviews:

Jesse, Vodeo – a movie renting platform
Anthony, Speakk – a mobile messenger that doesn’t consume traffic
Jan, AppyBee – a booking platform
Naseem, Mobytap – a business video review platform
Ali, TapeReal – a video social network

Categories
Uncategorized

How To Implement Screen Sharing in iOS App using ReplayKit and App Extension

Intro

Screen sharing – capturing user’s display and demonstrating it to peers during a video call. 

There’re 2 ways how you can implement screen sharing into your iOS app:

  1. Screen sharing in app. It suggests that a user can only share their screen from one particular app. If they minimize the app window, broadcasting will stop. It’s quite easy to implement.
  2. Screen sharing with extensions. This approach enables screen sharing from almost any point of the OS: e.g. Homescreen, external apps, system settings. But the implementation might be quite time-consuming.

In this article, we’ll share guides on both.

Screen sharing in app

Starting off easy – how to screen share within an app. We’ll use an Apple Framework, ReplayKit.

import ReplayKit

class ScreenShareViewController: UIViewController {

		lazy var startScreenShareButton: UIButton = {
        let button = UIButton()
        button.setTitle("Start screen share", for: .normal)
        button.setTitleColor(.systemGreen, for: .normal)
        return button
    }()
    
    lazy var stopScreenShareButton: UIButton = {
        let button = UIButton()
        button.setTitle("Stop screen share", for: .normal)
        button.setTitleColor(.systemRed, for: .normal)
        return button
    }()

		lazy var changeBgColorButton: UIButton = {
        let button = UIButton()
        button.setTitle("Change background color", for: .normal)
        button.setTitleColor(.gray, for: .normal)
        return button
    }()
    
    lazy var videoImageView: UIImageView = {
        let imageView = UIImageView()
        imageView.image = UIImage(systemName: "rectangle.slash")
        imageView.contentMode = .scaleAspectFit
        return imageView
    }()
}

Here we added it to the ViewController where recording, background color change buttons and imageView are – this is where the captured video will appear later.

the ViewController

To capture the screen, we address the RPScreenRecorder.shared() class and then call startCapture(handler: completionHandler:).

@objc func startScreenShareButtonTapped() {
		RPScreenRecorder.shared().startCapture { sampleBuffer, sampleBufferType, error in
				self.handleSampleBuffer(sampleBuffer, sampleType: sampleBufferType)
            if let error = error {
                print(error.localizedDescription)
            }
        } completionHandler: { error in
            print(error?.localizedDescription)
        }
}

Then the app asks for a permission to capture the screen: 

the permission pop-up

ReplayKit starts generating a CMSampleBuffer stream for each media type – audio or video. The stream contains the media fragment itself – the captured video – and all necessary information. 

func handleSampleBuffer(_ sampleBuffer: CMSampleBuffer, sampleType: RPSampleBufferType ) {
        switch sampleType {
        case .video:
            handleVideoFrame(sampleBuffer: sampleBuffer)
        case .audioApp:
//             handle audio app
            break
        case .audioMic:
//             handle audio mic
            break
        }
    }

The function converted into the UIImage type will then process each generated videoshot and display it on the screen.

func handleVideoFrame(sampleBuffer: CMSampleBuffer) {
        let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)!
        let ciimage = CIImage(cvPixelBuffer: imageBuffer)
        
        let context = CIContext(options: nil)
        var cgImage = context.createCGImage(ciimage, from: ciimage.extent)!
        let image = UIImage(cgImage: cgImage)
        render(image: image)
}

Here’s what it looks like:

generated frames

Captured screen broadcasting in WebRTC 

Usual setting: during a video call one peer wants to demonstrate the other what’s up on their screen. WebRTC is a great pick for it.

WebRTC connects 2 clients to deliver video data without any additional servers – it’s peer-to-peer connection (p2p). Check out this article to learn about it in detail. 

Data streams that clients exchange are media streams that contain audio and video streams. A video stream might be a camera image or a screen image.

To establish p2p connection successfully, configure a local mediastream that will further be delivered to the session descriptor. To do that, get an object of the RTCPeerConnectionFactory class and add the media stream packed with audio and video tracks to it.

func start(peerConnectionFactory: RTCPeerConnectionFactory) {

        self.peerConnectionFactory = peerConnectionFactory
        if self.localMediaStream != nil {
            self.startBroadcast()
        } else {
            let streamLabel = UUID().uuidString.replacingOccurrences(of: "-", with: "")
            self.localMediaStream = peerConnectionFactory.mediaStream(withStreamId: "\\(streamLabel)")
            
            let audioTrack = peerConnectionFactory.audioTrack(withTrackId: "\\(streamLabel)a0")
            self.localMediaStream?.addAudioTrack(audioTrack)

            self.videoSource = peerConnectionFactory.videoSource()
            self.screenVideoCapturer = RTCVideoCapturer(delegate: videoSource!)
            self.startBroadcast()
            
            self.localVideoTrack = peerConnectionFactory.videoTrack(with: videoSource!, trackId: "\\(streamLabel)v0")
            if let videoTrack = self.localVideoTrack  {
                self.localMediaStream?.addVideoTrack(videoTrack)
            }
            self.configureScreenCapturerPreview()
        }
    }

Pay attention to the video track configuration:

func handleSampleBuffer(sampleBuffer: CMSampleBuffer, type: RPSampleBufferType) {
        if type == .video {
            guard let videoSource = videoSource,
                  let screenVideoCapturer = screenVideoCapturer,
                  let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return }
            
            let width = CVPixelBufferGetWidth(pixelBuffer)
            let height = CVPixelBufferGetHeight(pixelBuffer)
            videoSource.adaptOutputFormat(toWidth: Int32(width), height: Int32(height), fps: 24)
            
            let rtcpixelBuffer = RTCCVPixelBuffer(pixelBuffer: pixelBuffer)
            let timestamp = NSDate().timeIntervalSince1970 * 1000 * 1000
            
            let videoFrame = RTCVideoFrame(buffer: rtcpixelBuffer, rotation: RTCVideoRotation._0, timeStampNs: Int64(timestamp))
            videoSource.capturer(screenVideoCapturer, didCapture: videoFrame)
        }
}

Screen sharing with App Extension

Since iOS is a quite closed and highly protected OS, it’s not that easy to address storage space outside an app. To let developers access certain features outside an app, Apple created App Extensions – external apps with access to certain relationships in iOS. They operate according to their types. App Extensions and the main app (let’s call it Containing app) don’t interact with each other directly but can share a data storing container. To ensure that, create an AppGroup on the Apple Developer website, then link the group with the Containing App and App Extension. 

containing app and extension relation
Scheme of data exchange between entities

Now to devising the App Extension. Create a new Target and select Broadcast Upload Extension. It has access to the recording stream and its further processing. Create and set up the App Group between targets. Now you can see the created folder with App Extension. There’re Info.plist, the extension file, and the swift SampleHandler file. There’s also a class with the same name written in SampleHandler that the recorded stream will process. 

The methods we can operate with are already written in this class as well: 

override func broadcastStarted(withSetupInfo setupInfo: [String : NSObject]?)
override func broadcastPaused() 
override func broadcastResumed() 
override func broadcastFinished()
override func processSampleBuffer(_ sampleBuffer: CMSampleBuffer, with sampleBufferType: RPSampleBufferType)

We know what they’re responsible for by their names. All of them, but the last one. It’s where the last CMSampleBuffer and its type arrives. In case the buffer type is .video, this is where the last shot will be.

Now let’s get to implementing screen sharing with launching iOS Broadcast. To start off, we demonstrate the RPSystemBroadcastPickerView itself and set the extension to call.

let frame = CGRect(x: 0, y: 0, width: 60, height: 60)
let systemBroadcastPicker = RPSystemBroadcastPickerView(frame: frame)
systemBroadcastPicker.autoresizingMask = [.flexibleTopMargin, .flexibleRightMargin]
if let url = Bundle.main.url(forResource: "<OurName>BroadcastExtension", withExtension: "appex", subdirectory: "PlugIns") {
    if let bundle = Bundle(url: url) {
           systemBroadcastPicker.preferredExtension = bundle.bundleIdentifier
     }
}
view.addSubview(systemBroadcastPicker)

Once a user taps “Start broadcast” the broadcast will start and the selected extension will process the state and the stream itself. But how will the Containing App know this? Since the storage container is shared, we can exchange data via the file system – e.g. UserDefaults(suiteName) and FileManager. With it we can set up a timer, check up the states within certain periods of time, record and read data along a certain track. An alternative to that is to launch a local web-socket server and address it. But in this article we’ll only cover exchanging via files.

Write the BroadcastStatusManagerImpl class that will record current broadcast status as well as communicate to the delegate on status alterations. We’ll check on the updated info using a timer with 0.5 sec frequency. 

protocol BroadcastStatusSubscriber: AnyObject {
    func onChange(status: Bool)
}

protocol BroadcastStatusManager: AnyObject {
    func start()
    func stop()
    func subscribe(_ subscriber: BroadcastStatusSubscriber)
}

final class BroadcastStatusManagerImpl: BroadcastStatusManager {

    // MARK: Private properties

    private let suiteName = "group.com.<YourOrganizationName>.<>"
    private let forKey = "broadcastIsActive"

    private weak var subscriber: BroadcastStatusSubscriber?
    private var isActiveTimer: DispatchTimer?
    private var isActive = false

    deinit {
        isActiveTimer = nil
    }

    // MARK: Public methods

    func start() {
        setStatus(true)
    }

    func stop() {
        setStatus(false)
    }

    func subscribe(_ subscriber: BroadcastStatusSubscriber) {
        self.subscriber = subscriber
        isActive = getStatus()

        isActiveTimer = DispatchTimer(timeout: 0.5, repeat: true, completion: { [weak self] in
            guard let self = self else { return }

            let newStatus = self.getStatus()

            guard self.isActive != newStatus else { return }

            self.isActive = newStatus
            self.subscriber?.onChange(status: newStatus)
        }, queue: DispatchQueue.main)

        isActiveTimer?.start()
    }

    // MARK: Private methods

    private func setStatus(_ status: Bool) {
        UserDefaults(suiteName: suiteName)?.set(status, forKey: forKey)
    }

    private func getStatus() -> Bool {
        UserDefaults(suiteName: suiteName)?.bool(forKey: forKey) ?? false
    }
}

Now we create samples of BroadcastStatusManagerImpl to the App Extension and the Containing App, so that they know the broadcast state and record it. The Containing App can’t stop the broadcast directly. This is why we subscribe to the state – this way, when it reports false, App Extension will terminate broadcasting, using the finishBroadcastWithError method. Even though, in fact, we end it with no error, this is the only method that Apple SDK provides for program broadcast termination. 

extension SampleHandler: BroadcastStatusSubscriber {
    func onChange(status: Bool) {
        if status == false {
            finishBroadcastWithError(NSError(domain: "<YourName>BroadcastExtension", code: 1, userInfo: [
                NSLocalizedDescriptionKey: "Broadcast completed"
            ]))
        }
    }
}

Now both apps know when the broadcast started and ended. Then, we need to deliver data from the last shot. To do that, we create the PixelBufferSerializer class where we declare the serializing and deserializing methods. In the SampleHandler’s processSampleBuffer method we convert CMSampleBuffer to CVPixelBuffer and then serialize it to Data. When serializing to Data it’s important to record the format type, height, width and processing increment for each surface in it. In this particular case we have two of them: luminance and chrominance, and their data. To get the buffer data, use CVPixelBuffer-kind functions.

While testing from iOS to Android we’ve faced this problem: the device just wouldn’t display the screen shared. It’s that Android OS doesn’t support the irregular resolution the video had. We’ve solved it by just turning it into 1080×720. 

Once having serialized into Data, record the link to the bytes gained into the file.

memcpy(mappedFile.memory, baseAddress, data.count)

Then create the BroadcastBufferContext class in the Containing App. Its operation logic is alike BroadcastStatusManagerImpl: the file discerns each timer iteration and reports on the data for further processing. The stream itself comes in 60 FPS, but it’s better to read it with 30 FPS, since the system doesn’t perform well when processing in 60 FPS due to lack of the resource. 

func subscribe(_ subscriber: BroadcastBufferContextSubscriber) {
        self.subscriber = subscriber

        framePollTimer = DispatchTimer(timeout: 1.0 / 30.0, repeat: true, completion: { [weak self] in
            guard let mappedFile = self?.mappedFile else {
                return
            }

            var orientationValue: Int32 = 0
            mappedFile.read(at: 0 ..< 4, to: &orientationValue)
            self?.subscriber?.newFrame(Data(
                bytesNoCopy: mappedFile.memory.advanced(by: 4),
                count: mappedFile.size - 4,
                deallocator: .none
            ))
        }, queue: DispatchQueue.main)
        framePollTimer?.start()
    }

Deserialize it all back to CVPixelBuffer, likewise we serialized it but in reverse. Then we configure the video track by setting up the extension and FPS.

videoSource.adaptOutputFormat(toWidth: Int32(width), height: Int32(height), fps: 60)

Now add the frame RTCVideoFrame(buffer: rtcpixelBuffer, rotation: RTCVideoRotation._0, timeStampNs: Int64(timestamp)). This track goes to the local stream.

localMediaStream.addVideoTrack(videoTrack)

Conclusion 

Implementing screen sharing in iOS is not that easy as it may seem. Reservedness and security of the OS force developers into looking for workarounds to deal with such tasks. We’ve found some – check out the result in our Fora Soft Video Calls app. Download on AppStore.

Categories
Uncategorized

How to Save Time & Money By Sharing Your Source Code With Devs

When you approach us with not just a project idea, but an existing code, we’ll ask you to share it with us. Don’t worry, it will help you save lots of time and maybe improve a product. Read to find out how!

Many customers come to us with a project already at some stage of readiness. It may be a draft version, which needs the hands of professionals before the official release; or the already established product, which we’re glad to improve.

Anyway, we ask clients to share the source code of an existing project. As a rule of thumb, this doesn’t cause any difficulties. The opposite happens as well: our customers wonder why we need existing code as we will create our own, and what are the risks? Should you share your source code with a new developer / between development teams or not?

Let us explain it all to you.

How can we improve the application using your code?

  • Test the whole platform, not just our part 

Even if the task is a standalone module that adds new features to an existing project, there’s only 1 criterion for readiness: a well-functioning system.

To confirm that the new module is well integrated into the existing system, we need to accurately recreate the real environment (a test environment) and test it comprehensively. What if a new feature – such as the ability to translate speech on the fly – interferes with existing functionality? 

  • By ensuring full compatibility

It happens that the existing code was written quite a long time ago and uses previous versions of various tools. In this case, the only way to be sure that the new module will work well in a live infrastructure is to test them together and resolve possible contradictions.

  • By ensuring quick support for the new functionality

Integration of several systems (e.g. an existing project and a new module) always requires coordinated efforts of the teams: the one which supports the live product and the one which created the new functionality. In order for the integration to go quickly and for the teams to interact effectively, it is necessary for both parties to navigate both parts of the project. Then your team can clarify requirements at any time, and our team can implement them.

  • (Maybe) we’ll improve what’s already working

We have 17 years of experience in developing online multimedia platforms for various purposes. It often happens that we have effective ways to solve important problems – from scaling to saving traffic – that will make the already implemented functionality more reliable and less demanding.

In other words, being able to open the existing source code is the main guarantee that developing add-ons to the live platform will go as fast as possible and save you money and time before release.

Why you don’t have to worry about your code

You don’t send your child to an unknown kindergarten without being sure that nothing bad will happen to him. Your code is your baby, and we guarantee that, should you share the code with us, we won’t do things such as:

  • We won’t re-use it

Our clients’ intellectual property is protected by both the law and our reputation.

The contract we conclude with our clients includes a clause about non-disclosure of the data constituting the intellectual property (NDA). This means that all the code that we receive from a client is used to perform his and only his tasks.

  • We won’t edit it without the client’s knowledge

In all projects, we adhere to strict rules for updating code in the client repository. Any new code is uploaded to a separate branch, and it is up to the customer to decide whether or not to make these changes to the main branches of the project. This means one thing: whether it’s just our team working on the platform, or several independent teams of specialists, the client always has the final say.

But I really can’t share the source code! What to do then?

Of course, there are situations where access to the source code is really impossible. Or it simply doesn’t exist, because we are talking about a hardware platform that requires software.

If access to the source code is impossible because it belongs to a third party. 

For example, you are developing a system for a large corporate contractor, and you’ve decided to turn to a video communications specialist.

Our analysts and developers will help you formulate a detailed requirements specification: how, where, in what format and what data should the component created by us transmit? Together we will draw up documentation on which we can work.

If your project uses proprietary solutions, as a rule, they are documented in detail. In this case, access to documentation is an important factor for fast development and easy integration.

If there is no access to the sources, because the previous developers have handed over the finished product, and it is impossible to contact them

Development team rushes to the rescue! We will try to extract as much data as possible from the finished system and restore the source code. The features of JavaScript, our main development language, and our experience make it possible.

If you have hardware, but no software for it yet.

The main thing we need is a sample and as detailed specifications as possible: how do your devices work, what OS do they use, what is their processing power? This data will help us to create software to perform the required tasks on the existing devices.

We hope our explanation was convincing enough for you not to hesitate to share the code.  If you have any questions left, please don’t hesitate to drop us a message via the contact form, and we’ll get back to you ASAP.

Categories
Uncategorized

How to Choose a Live Streaming Protocol? RTMP vs HLS vs WebRTC

cover image

When streaming a video, the delivery mostly depends on which live streaming protocol is used. Each protocol performs differently in terms of latency, scalability, and supporting devices. While there’s no universal solution to each live streaming need, you still have freedom to pick the best match for your specific requirements.

How does streaming work?

For the end user, live streaming is all about a quasi-real experience with a screen and a camera standing in-between them and the source. In reality, there’s much more backstage. 

It does start with capturing whatever is about to go live on camera. It does end with playing the content on an end-user device. But to make this happen, it takes four more milestones to complete.

  1. Once having recorded even a byte of information, video and audio get compressed with an encoder. In short, encoder converts raw media material into a piece of digital information that can be displayed on many devices. It also reduces the file size from GBs to MBs to ensure quick data delivery and playback.
  2. Compressed data then gets ingested into a streaming platform for further processing. 
  3. The platform resource transcodes the stream and packages it into its final format and protocol. The latter is basically the way data travels from one communicating system to another. 
  4. To get to its end user, the stream is then delivered across the internet, in most cases via a content delivery network (CDN). In turn, CDN is a system of servers located in different physical locations.

Aaand — cut! Here’s where a user sees the livestream on their device. A long way to go, huh? But in fact, this journey may take less than 0,5 second or 1-2 minutes. This is called latency and it varies from one protocol to another. As well as many other parameters. 

There’re quite a few live streaming protocols to choose from, but we’ll elaborate on three most commonly used ones: RTMP, HLS and WebRTC. In short, here’s the difference: 

protocols comparison

Now in detail.

RTMP

Real-Time Messaging Protocol (RTMP) is probably the most widely supported media streaming protocol from both sides of the process. It’s compatible with many professional recording devices and also quite easy to ingest into streaming platforms like YouTube, Twitch, and others. 

Supporting low-latency streaming, RTMP delivers data at roughly the same pace as cable broadcasting — it takes only 5 seconds to transmit information. This is due to using a firehose approach that builds a steady stream of available data and delivers it to numerous users in real time. It just keeps flowing!

Yet, this video streaming protocol is no longer supported by browsers. This results in additional conversion and transcoding the stream into a HTTP-based technology, prolonging the overall latency to 6-30 seconds. 

HLS

HTTP Live Streaming, developed by Apple as a part of QuickTime, Safari, OS X, and iOS software, is a perfect pick if you stream to a large audience, even millions at a time. HLS is supported by all browsers and almost any device (set-top boxes, too). 

The protocol supports adaptive bitrate streaming which provides the best video quality no matter the connection, software, or device. Basically it’s key to the best viewer experience.

Usually, to cover a large audience with broad geography stably and with the lowest latency, CDN is used. 

The only major drawback of HLS seems to be the latency — prioritizing quality, it may reach 45 seconds when used end-to-end. 

Apple presented the solution in 2019: Low Latency HLS shrinks the delay to less than 2 seconds and is currently supported by all browsers, as well as Android, Linux, Microsoft and, obviously, macOS devices, several set-top boxes and Smart TVs. 

WebRTC

RTC in WebRTC stands for “real-time communication” which suggests this protocol is perfect for an interactive video environment. With the minimum latency possible (less than 0,5 second) WebRTC is mostly used for video conferencing with not so many participants (usually 6 or less). We use it a lot in our projects for video conferencing. Check out the ProVideoMeeting or CirrusMED portfolios.

But media servers like Kurento allow WebRTC scaling to an audience of up to 1 million viewers. Yet, a more popular approach would be to use WebRTC as an ingest and repackage it into more scalable HLS. 

Apart from providing the absolute minimum latency possible, WebRTC doesn’t require any additional equipment for a streamer to do the recording. 

Conclusion

Thus, when choosing a live streaming protocol for your streaming goals, ask yourself these questions:

  • What kind of infrastructure am I willing to establish or already have? What recording devices and software are at my disposal?
  • What kind of content I want to deliver and which latency is acceptable? How “live” do I want the streaming to be?
  • What system scalability do I expect? How many users I guesstimate will watch the stream at a time? 

One protocol may not satisfy all your needs. But a good mix of two can. Hit us up to get a custom solution to turn your ideas into action.

Categories
Uncategorized

How We Optimized Analytics Process in Software Development Company

Wireframing is one of the key stages of the analytics process at Fora Soft. Interactive prototype being a rough image of the future product gives the team space to alter functionality at the earliest stage before the development. It also tributes to the client’s confidence that we’re on the same page regarding the product view.

axure wireframe
Axure prototype example

Developing the systems and platforms of various structures, for various audiences and user needs, you still have to design core functionality and basic scenarios all the time. 

Imagine how rich the difference is between the functionality of an e-learning platform, a booking app, and a streaming service. However, most likely all of them will feature a sign-up scenario, editing a user profile and sending messages to the chat.

Having analyzed the wide scope of company projects, Fora Soft analyst team has distinguished the most common user scenarios and created the completed wireframe templates for them, following the usability guidelines and the best practices in web and mobile UX design.

These ready-to-use wireframe templates contribute to the analytics process optimization in Fora Soft. They allow us to dedicate more time and effort to the product’s killer features rather than its basic functionality. The templates are concise and flexible enough to be applied to different kinds of systems.

Among them, you will find a video conference template. Our area of expertise is multimedia software such as systems for online education, sports, teamwork, telemedicine, and video surveillance. 
Check the Fora Soft wireframe library here and try its templates to design your amazing projects.

Download the kit right here:

Categories
Uncategorized

The analytical stage of software development at Fora Soft

At Fora Soft, the first person you will work with on your idea will be an analyst. As bringing your concept for a unique product to reality is typically one of the most difficult challenges for entrepreneurs, the company needs a professional team. First steps require an analyst who can lead you through all the challenges along the way. In this article, we will observe the value that analysts at Fora Soft bring to your project as well as the possible negative scenarios of missing a system analytic.

From Idea to MLP (Minimum Loveable Product)

Product development usually follows a process separated into stages or steps, through which a company conceives:

  •  product concept (idea generation) 
  •  researches (product validation ensures you’re creating a product people will pay for and that you won’t waste time, money, and effort on an idea that people don’t need)
  •  project planning
  •  prototypes
  •  designs
  •  development
  •  tests
  •  launch into the market

What if it seems that the idea is already sharpened? Why do I need the analytics process then?

According to Info Tech research, poor requirements are the reasons for 70% of unsuccessful software projects. This may lead to financial losses, wasted time and effort, and disillusionment (we will take a closer look at all the possible circumstances below). You may avoid these stumbling blocks and assure the adoption of best-practice approaches by working closely with an analyst at the outset of a project.

Possible outcomes of skipping the analysis stage

Let me showcase you the list of possible struggles you can face due to skipping the analytical stage of software development:

Time

  • Inability to logically structure a development process
  • Postponement of a current release
  • Incapacity of long-term planning

Money

  • Waste of development hours on functionality redesign
  • Radical change of initial estimation
  • Unrealistic time and costs estimation due to lack of requirements decomposition 

Process

  • Team members may idle and be removed from the project due to lack of tasks at the current time period
  • The team doesn’t include the essential specialist as there were no special requirements gathered for the particular feature in the very beginning
  • Some tasks may require parallel development
  • Constant hole patching instead of building a clear coherent vision

Relationship damage

  • with the customer 
  • within the team

Documentation

  • Customer and team have different product vision
  • Fragmented documentation 

Result

  • Choice of more expensive functionality instead of simple and elegant solutions
  • Architectural limitations
  • Inconsistency or the requirements and logical holes

Preparation stage

So, as we have identified the significance of the analytical stage of software development, let’s dive into the details. 

First of all, we will review the initial requirements, understand your vision and product concept. As the next step, the analyst will provide research on: 

  • Target audience and their pains
  • Competitors
  • Best practises in the industry

That allows us to find unique selling points. A unique selling point describes your company’s distinct market position, getting to the heart of your offering: the value you provide and the problem you address. A good USP clearly articulates a distinct benefit – one that other competitors do not provide – that distinguishes you from the competition.

Requirement analysis

At the next stage of the analysts phase, we will start to design the system from requirement preparation. Requirements analysis is a vital procedure that determines the success of a system or software project. Functional and non-functional requirements are the two sorts of requirements.

Non-functional requirements: These are the quality limitations that the system must meet in accordance with the project contract. The priority or extent to which these aspects are incorporated varies depending on the project. Non-behavioral requirements are another name for them.

Functional Requirements: These are the requirements that the end user directly requests as basic system facilities. These are expressed or described as input to be delivered to the system, operation to be conducted, and expected output. In contrast to non-functional requirements, they are essentially the user-specified criteria that can be seen immediately in the finished product.

Functional requirements are carried out in the form of user stories, which are summaries of needs or queries created from the perspective of a particular product user. All stories will be divided into subsections – epics. The main goal after that is the maximum added value with minimal applied effort. It can be achieved by task prioritization. 

Wireframing

After a couple of iterations and clarifying the requirements, we will do the wireframe. 

Wireframe is a form of interactive prototype that has no user interface, no colors, fonts, or style – just functionality. Consider wireframes to be the skeleton of your product. They give you a good concept of where everything will end up by roughly shaping the final product. In a wireframe stage, it is easier and less expensive to evaluate and alter the structure of the essential pages. Iterating the wireframes to a final version will provide the client and design team confidence that the page/tab is meeting user needs while also achieving the primary business and project goals. Check the example via the link.

On this stage, the analyst reviews UX industry’s best practices as well as searches for a unique selling point. For mobile products, we are referring to Apple Human Interface Guidelines and Google Material Design. Guidelines were developed to expedite the process of resolving user pains. Guidelines for mobile apps specify navigation and interaction concepts, interface components and their styles, typography and iconography used, color palettes, and much more. Furthermore, as everything described in the guidelines is frequently already implemented as an element in the code, the developer does not need to spend time on creating it from scratch.

During these stages, business analysts will be consulting technicians, designers, marketers and other specialists to find the most suitable and elegant solutions.

Testing

Once the wireframe is entirely adhering to the user stories and you are satisfied with the requirements, we are going to the QA stage to ensure quality and consistency of the prototype.

  • Completeness. A set of requirements is regarded as complete if all of its basic pieces are represented and each component is completed with a logical end.
  • Unambiguity. Each component must be clearly and precisely stated, allowing for a distinct interpretation. The request should be legible and comprehensible.
  • Consistency. Requirements should not be in conflict with one another or wireframe.
  • Validity. Requirements should meet the expectations and needs of the final user.
  • Feasibility. The scenarios must be possible to implement.
  • Testability. We should be able to create economically feasible and simple-to-use tests for each need to indicate that the tested product meets the required functionality, performance, and current standards. This implies that each claim must be measured, and testing must be carried out under appropriate conditions.

Testing requirements is a proven way to avoid problems during the development stage. It is at this point that continuous testing begins in order to ensure the requisite quality of the created product and to avoid any business risks. It’s always better to find all hidden dangers on the analytical stage rather than during software development.

Concept design

Optionally, as a part of the analytical process, you may request concept design of the product. Conceptual design is an early stage of the design process in which we establish the broad outlines of something’s purpose and form.  It entails comprehending people’s needs and determining how to address them through products.  These are pictures that will demonstrate the concept’s “mood” and colors in further depth. 

Concept

Update logo, if you don’t have your own

Corporate identity elements: patterns, slides with a slogan that reflects the concept. The options and number of pictures will depend on the concept and product.

UI of the 1st-2nd main screens of the application/platform.

You can check the example via the link.

This is a final stage of the analytical process. After that you will have a full clear vision and be totally ready for the development process. As a next step, you will receive an estimation from our sales manager.

Conclusion

The better the team understands the big picture, the better the final product will be. It is crucial to have solid relationships and a deep level of understanding between the team and customer, and that is what an analyst can fully provide. The price and time estimates you will get from the development team are as precise as the requirements are. After the analytics, it’s possible to give an estimate with +- ~10% deviation. These will help to assure improved cost management, delivery, and meeting business goals. 

So if you feel like talking to our analysts and getting your wireframe, don’t hesitate to hit us up using the contact form.

Categories
Uncategorized

Why We Have To Know The Number of Active Users In Your App

When clients initially come to us, some of the first questions they hear is: “How many people do you expect to be using your app in the first month?”. Or “How many are likely to be using it simultaneously?”

Many people answer reluctantly and uncertainly, ranging from “why do you need it” to “you’re developers, you know better”. Meanwhile, the exact answer can save the client money, and quite a lot of it, actually. Sometimes even help earn it.. 

Is it possible to save money or even make more by answering this question?

Now, let’s talk money since we want business to be profiable, right? Information about the number of users not only helps your project team, but also helps you save or get more money. How?

By knowing how many people will use the platform, we can:

  • Calculate the necessary server capacity: the client won’t have to overpay for unused resources;
  • Build a scaling system architecture 
  • Estimate the costs of load-testing
  • Build a development plan that will allow the project to go to market (or present a new version to users) as quickly as possible.

So, what we’re doing here? We’re saving money by eliminating unnecessary costs now and planning the implementation of new features in the future.

Also, this helps to make money by ensuring a quicker TTM (time-to-market). And provide the confidence that the platform is meeting its goals.

What exactly are we asking?

Depending on the specifics of the platform, it’s important for us to know:

– The maximum number of platform users per month;

– The maximum number of users on the platform online at one time;

– What exactly are users doing on the platform: e.g., posting stuff, making calls, logging in to the game — how many times a day? 

– The perceived dynamics of audience growth

What if I really don’t know?

If your project is already live, chances are there are analytics out there. Google Analytics or its counterparts allow you to estimate the number of users quickly and accurately. 

If not, you can rely on more technical data: information from databases, server load statistics, or summaries from the cloud provider console, and so on.

If you need our team to create a project from scratch, it makes sense to look at competitors’ statistics, for example, using the service SimilarWeb. If for some reason this is not possible, rely on 1000 active users – our experience suggests that it’s enough in the first months of the life of the product.

And, of course, in both cases you should consult our analysts. We’ll help you gather the necessary data and draw conclusions.

Is this important for all projects?

Yes, for all of them. It’s especially critical for systems that meet at least one of these criteria:

  • Large inbound/outbound traffic: users uploading and downloading HD video, video conferencing for 3+ users,
  • There is a requirement to ensure minimal latency: users are playing an online game, rehearsing musical instruments over a video call, or mixing a DJ set,
  • The application involves long and resource-intensive operations: compressing, converting or processing video, archiving files, routing video/audio calls, processing or generating data with neural networks.

Why not make it with the expectation of many thousands and very intense online at once and for everyone?

Firstly, a platform like that will get into production later.

If we know that only a small audience (usually called early adopters) will be using it in the first months, it is more reasonable and profitable not to postpone the launch until the balancing and scaling systems are ready and tested under load. 

Secondly, the larger the estimated load, the more expensive the system operation gets. Especially if it runs in the cloud. Focusing on big online means not only being able to scale, but having enough spare capacity here and now to handle a significant influx of users at any given time. That is, to keep a large and expensive server always on, not a small cheap one.

Thirdly, this calculation isn’t applicable to all projects at all.

For closed corporate platforms, it simply makes no sense to develop a product for an army of thousands of users.

What does the developer do with this data?

The developer will understand:

  • What kind of server you need: on-premise, cloud (AWS, Hetzner, Google Cloud, AliCloud), or a whole network of servers
  • Whether it is possible and necessary to transfer some of the load to the user device (client)
  • Which of the optimization and performance-related tasks need to be implemented immediately and which can be deferred to later sprints

Offtopic: what is the difference between server load and client load?

A simple example: let’s say we’re doing our own instagram. The user shoots a video, adds simple effects, and posts the result on their feed.

If the goal is to get to the first audience quickly and economically, the pilot build can do almost everything on the server.

Pros:

  • There’s no risk of getting bogged down by platform-specific limitations: video formats, load limits, and other nuances don’t bother us. Everything is handled centrally, so you can quickly make a product for all platforms and release it simultaneously
  • There are no strict requirements for client devices: it’s easier to enter growing markets, such as Africa, SEA, Latin America. Even a super cheap phone, of which there are many in the mentioned regions, can do it
  • Our “non-Instagram” applications for certain platforms, such as web and mobile OS, are very simple. Authorization, feed, download button, and that’s it. 

And if the goal is to give full functionality to a large active audience at once, heavy server calculations lose appeal: it makes sense to harness the power of client devices immediately.

Pros:

  • Fewer servers and operating costs for the same number of users
  • The user feels that the application is more responsive. In addition, if there are already a lot of clients and we have added complex new features, the responsiveness of the platform will not become lower
  • Users feel more comfortable experimenting with new functionality: it’s implemented on the client, so delays are minimal
  • Internet may not be required during content processing – it saves traffic
  • The uploaded video is published faster: it does not need to be queued for server processing
  • The easier and faster the individual operations on the server, the easier and cheaper it is to scale the server. It’s especially critical when there is a sudden influx of new users

A compromise, which often turns out to be the best option – the one that doesn’t shift the whole load on one of the parties. For example, video processing tasks, such as applying effects or graphics, are often performed on the client, while the conversion of mobile video into the required formats and resolutions is performed on the server. And in this case, the distribution of tasks between the client device and the server also depends on the planned scope.

What if we develop just a component for a live project? 

In the case of extending an already existing product, it’s necessary to find out where tasks are currently processed: on the device or on the server.

Then, based on the purpose of the future component and the forecast of the number of users and their activity on the platform after it appears, the developer will understand whether to improve the current architecture or migrate to a more efficient one.

So in the end, why are we asking about the number of users?

It all comes down to efficiency and saving your resources and money. We need to have as accurate knowledge as possible about the product’s scope and workload. It will help your project team to better plan the launch, allocate costs, and make the system more reliable in the long run.

Categories
Uncategorized

How Digital Video as a Technology Works

tv with different types of video

In this article, we’ll try to explain what digital video is and how it works. We’ll be using a lot of examples, so even if you wanna run away before reading something difficult – fear not, we’ve got you. So lean back and enjoy the explanation on video from Nikolay, our CEO. 😉

Analog and digital video

Video can be analog and digital.

All of the real world information around us is analog. Waves in the ocean, sound, clouds floating in the sky. It’s a continuous flow of information that’s not divided into parts and can be represented as waves. People perceive exactly analog information from the world around them.    

The old video cameras, which recorded on magnetic cassettes, recorded information in analog form. Reel-to-reel tape and cassette recorders worked on the same principle. Magnetic tape was passed through the turntable’s magnetic heads, and this allowed sound and video to be played. Vinyl records were also analog. 

Such records were played back strictly in the order in which they were recorded. Further editing was very difficult. So was the transfer of such recordings to the Internet.

With the ubiquity of computers, almost all video is in digital format, as zeros and ones. When you shoot video on your phone, it’s converted from analog to digital media and stored in memory, and when you play it back, it’s converted from digital to analog. This allows you to stream your video over a network, store it on your hard drive, and edit and compress it.

What a digital video is made of

Video consists of a sequence of pictures or frames that, as they change rapidly, make it appear as if objects are moving on the screen.

This here is an example of a how a video clip is done.

What is Frame Rate

Frames on the screen change at a certain rate. The number of frames per second is the frame rate or framerate. The standard for TV is 24 frames per second, and 50 frames per second for IMAX in movie theaters.

The higher the number of frames per second, the more detail you can see with fast-moving objects in the video. 

Check out the difference between 15, 30, and 60 FPS.

What is pixel

All displays on TVs, tablets, phones and other devices are made up of little glowing bulbs – pixels. Let’s say that each pixel can display one color (technically different manufacturers implement this differently). 

In order to display an image on a display, it is necessary for each pixel on the screen to glow a certain color. 

Thanks to this technical device of screens, in digital video each frame is a set of colored dots or pixels. 

The number of such dots horizontally and vertically is called the picture resolution. The resolution is recorded as 1024×768. The first number is the number of pixels horizontally and the second number, vertically. 

The resolution of all frames in a video is the same and this in turn is called the video resolution.

Let’s take a closer look at a single pixel. On the screen it’s a glowing dot of a certain color, but in the video file itself a pixel is stored as digital information (numbers). With this information the device will understand what color the pixel should light up on the screen. 

What are color spaces

There are different ways of representing the color of a pixel digitally, and these ways are called color spaces. 

Color spaces are set up so that any color is represented by a point that has certain coordinates in that space. 

For example, the RGB (Red Green Blue) color space is a three-dimensional color space where each color is described by a set of three coordinates – each of them is responsible for red, green and blue colors. 

Any color in this space is represented as a combination of red, green, and blue.

how color spaces work

Here is an example of an RGB image decomposed into its constituent colors:

what is RGB

There are many color spaces, and they differ in the number of colors that can be encoded with them and the amount of memory required to represent the pixel color data.

The most popular spaces are RGB (used in computer graphics), YCbCr (used in video), and CMYK (used in printing)

CMYK is very similar to RGB, but has 4 base colors – Cyan, Magenta, Yellow, Key or Black.

RGB and CMYK spaces are not very efficient, because they store redundant information.  

Video uses a more efficient color space that takes advantage of human vision.

The human eye is less sensitive to the color of objects than it is to their brightness.

how human eye understand brightness

On the left side of the image, the colors of squares A and B are actually the same. It just seems to us that they are different. The brain forces us to pay more attention to brightness than to color. On the right side there is a jumper of the same color between the marked squares – so we (i.e., our brain) can easily determine that, in fact, the same color is there.

Using this feature of vision, it is possible to display a color image by separating the luminosity from the color information. Subsequently, half or even a quarter of the color information can simply be discarded in compression (representing the luminosity with a higher resolution than the color). The person will not notice the difference, and we will essentially save on storage of the information about color.

About how exactly color compression works, we will talk in the next article.

The best known space that works this way is YCbCr and its variants: YUV and YIQ.  

Here is an example of an image decomposed into components in YCbCr. Where Y’ is the luminance component, CB and CR are the blue and red color difference components.

how YCbCr works

It is YCbCr that is used for color coding in video. Firstly, this color space allows compressing color information, and secondly, it is well suited for black and white video (e.g. surveillance cameras), as the color information (CB and CR) can simply be omitted.

 

What is Bit Depth

The more bits, the more colors can be encoded, and the more memory space each pixel occupies. The more colors, the better the picture looks.

For a long time it has been standard for video to use a color depth of 8 bits (Standard Dynamic Range or SDR video). Nowadays, 10-bit or 12-bit (High Dynamic Range or HDR video) is increasingly used.

compare SDR and HDR video

It should be taken into account that in different color spaces, with the same number of bits allocated per pixel, you can encode a different number of colors. 

What is Bit Rate

Bit rate is the number of bits in memory that one second of video takes. To calculate the bit rate for uncompressed video, take the number of pixels in the picture or frame, multiply by the color depth and multiply by the number of frames per second

1024 pixels X 768 pixels X 10 bits X 24 frames per second = 188743680 bits per second

That’s 23592960 bytes, 23040 kilobytes or 22.5 megabytes per second.

Those 5 minute videos would take up 6,750 megabytes or 6.59 gigabytes of memory.

This brings us to why video compression methods are needed and why they appeared. Without compression it’s impossible to store and transmit that amount of information over a network. YouTube videos would take forever to download.

Conclusion

This is a quick introduction in the world of video. Now that we know what it consists of and the basics of its work, we can move on to the more complicated stuff. Which will still be presented in a comprehensive way 🙂

In the next article I’ll tell you how video compression works. I’ll talk about lossless compression and lossy compression. 

Categories
Uncategorized

Advanced iOS App Architecture Explained on MVVM with Code Examples

MVVM iOS architecture

How do you save your developers’ time, especially when you have to move between several projects? Is it possible to create a template of sorts for the new devs to use?

The Fora Soft iOS department decided to create a unified architecture for apps. In 16 years of work, we have developed more than 60 applications. We regularly had to spend weeks digging into code to understand the structure and operation of another project. Some projects we created as MVP, some as MVVM, some as our own. Switching between projects and reviewing other developers’ code increased our development time by several more hours. When went on to creating an iOS app architecture, we first defined the main goals to achieve:

Simplicity and speed. One of the main goals is to make developers’ lives easier. To do this, the code must be readable and the application must have a simple and clear structure. 

Quick immersion in the project. Outsourced development doesn’t provide much time to dive into a project. It is important that when switching to another project, it does not take the developer much time to learn the application code. 

Scalability and extensibility. The application under development must be ready for large loads and be able to easily add new functionality. For this it is important that the architecture corresponds to modern development principles, such as SOLID, and the latest versions of the SDK

Constant development. You can’t make a perfect architecture all at once, it comes with time. Every developer contributes to it – we have weekly meetings where we discuss the advantages and disadvantages of the existing architecture and things we would like to improve.

The foundation of our architecture is the MVVM pattern with coordinators 

Comparing popular MV(X) patterns, we settled on MVVM. It seemed to be the best because of good speed of development and flexibility.

MVVM stands for Model, View, ViewModel:

  • Model – provides data and methods of working with it. Request to receive, check for correctness, etc.
  • View – the layer responsible for the level of graphical representation.
  • ViewModel – The mediator between the Model and View. It is responsible for changes of Model, reacting on user’s actions performed on View, and updates View, using changes from Model. The main distinctive feature from other intermediaries in MV(X) patterns is the reactive bindings of View and ViewModel, which significantly simplifies and reduces the code of working with data between these entities.

Along with the MVVM, we’ve added coordinators. These are objects that control the navigational flow of our application. They help to:

  • isolate and reuse ViewControllers
  • pass dependencies down the navigation hierarchy
  • define the uses of the application
  • implement Deep Links

We also used the DI (Dependency Enforcement) pattern in the iOS development architecture. This is a setting over objects where object dependencies are specified externally, rather than created by the object itself. We use DITranquillity, a lightweight but powerful framework with which you can configure dependencies in a declarative style. 

Let’s break down our advanced iOS app architecture using a note-taking application as an example. 

Let’s create the framework for the future application. Let’s implement the necessary protocols for routing.

import UIKit
 
protocol Presentable {
    func toPresent() -> UIViewController?
}
 
extension UIViewController: Presentable {
    func toPresent() -> UIViewController? {
        return self
    }
}
protocol Router: Presentable {
  
  func present(_ module: Presentable?)
  func present(_ module: Presentable?, animated: Bool)
  
  func push(_ module: Presentable?)
  func push(_ module: Presentable?, hideBottomBar: Bool)
  func push(_ module: Presentable?, animated: Bool)
  func push(_ module: Presentable?, animated: Bool, completion: (() -> Void)?)
  func push(_ module: Presentable?, animated: Bool, hideBottomBar: Bool, completion: (() -> Void)?)
  
  func popModule()
  func popModule(animated: Bool)
  
  func dismissModule()
  func dismissModule(animated: Bool, completion: (() -> Void)?)
  
  func setRootModule(_ module: Presentable?)
  func setRootModule(_ module: Presentable?, hideBar: Bool)
  
  func popToRootModule(animated: Bool)
}

Configuring AppDelegate and AppCoordintator

A diagram of the interaction between the delegate and the coordinators

In App Delegate, we create a container for the DI. In the registerParts() method we add all our dependencies in the application. Next we initialize the AppCoordinator by passing window and container and calling the start() method, thereby giving it control.

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
    private let container = DIContainer()
    
    var window: UIWindow?
    private var applicationCoordinator: AppCoordinator?
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        
        registerParts()
        
        let window = UIWindow()
        let applicationCoordinator = AppCoordinator(window: window, container: container)
        self.applicationCoordinator = applicationCoordinator
        self.window = window
        
        window.makeKeyAndVisible()
        applicationCoordinator.start()
        
        return true
    }
 
    private func registerParts() {
        container.append(part: ModelPart.self)
        container.append(part: NotesListPart.self)
        container.append(part: CreateNotePart.self)
        container.append(part: NoteDetailsPart.self)
    }
}

The App Coordinator determines on which script the application should run. For example, if the user isn’t authorized, authorization is shown for him, otherwise the main application script is started. In the case of the notes application, we have 1 scenario – displaying a list of notes. 

We do the same as with App Coordinator, only instead of window, we send router.

final class AppCoordinator: BaseCoordinator {
    private let window: UIWindow
    private let container: DIContainer
    
    init(window: UIWindow, container: DIContainer) {
        self.window = window
        self.container = container
    }
    
    override func start() {
        openNotesList()
    }
    
    override func start(with option: DeepLinkOption?) {
        
    }
    
    func openNotesList() {
        let navigationController = UINavigationController()
        navigationController.navigationBar.prefersLargeTitles = true
        
        let router = RouterImp(rootController: navigationController)
        
        let notesListCoordinator = NotesListCoordinator(router: router, container: container)
        notesListCoordinator.start()
        addDependency(notesListCoordinator)
        
        window.rootViewController = navigationController
    }
}

In NoteListCoordinator, we take the dependency of the note list screen, using the method container.resolve(). Be sure to specify the type of our dependency, so the library knows which dependency to fetch. Also set up jump handlers for the following screens. The dependencies setup will be presented later.

class NotesListCoordinator: BaseCoordinator {
    private let container: DIContainer
    private let router: Router
    
    init(router: Router, container: DIContainer) {
        self.router = router
        self.container = container
    }
    
    override func start() {
        setNotesListRoot()
    }
    
    func setNotesListRoot() {
        let notesListDependency: NotesListDependency = container.resolve()
        router.setRootModule(notesListDependency.viewController)
        notesListDependency.viewModel.onNoteSelected = { [weak self] note in
            self?.pushNoteDetailsScreen(note: note)
        }
        notesListDependency.viewModel.onCreateNote = { [weak self] in
            self?.pushCreateNoteScreen(mode: .create)
        }

Creating a module

Each module in an application can be represented like this:

Module scheme in iOS application architecture

The Model layer in our application is represented by the Provider entity. Its layout is

Provider scheme in apple app architecture

The Provider is an entity in iOS app architecture, which is responsible for communicating with services and managers in order to receive, send, and process data for the screen, e.g. to contact services to retrieve data from the network or from the database.

Let’s create a protocol for communicating with our provider by mentioning the necessary fields and methods. Let’s create a structure ProviderState, where we declare the data on which our screen will depend. In the protocol, we will mention fields such as Current State with type ProviderState and its observer State with type Observable<ProviderState> and methods to change our Current State. 

Then we’ll create an implementation of our protocol, calling as the name of the protocol + “Impl”. CurrentState we mark as @Published, this property wrapper, allows us to create an observable object which automatically reports changes. BehaviorRelay could do the same thing, having both observable and observer properties, but it had a rather complicated data update flow that took 3 lines, while using @Published only took 1. Also set the access level to private(set), because the provider’s state should not change outside of the provider. The State will be an observer of CurrentState and will broadcast changes to its subscribers, namely to our future View Model. Do not forget to implement the methods that we will need when working on this screen.

struct Note {
    let id: Identifier<Self>
    let dateCreated: Date
    var text: String
    var dateChanged: Date?
}
 
protocol NotesListProvider {
    var state: Observable<NotesListProviderState> { get }
    var currentState: NotesListProviderState { get }
}
 
class NotesListProviderImpl: NotesListProvider {
    let disposeBag = DisposeBag()
    
    lazy var state = $currentState
    @Published private(set) var currentState = NotesListProviderState()
    
    init(sharedStore: SharedStore<[Note], Never>) {
        sharedStore.state.subscribe(onNext: { [weak self] notes in
            self?.currentState.notes = notes
        }).disposed(by: disposeBag)
    }
}
 
struct NotesListProviderState {
    var notes: [Note] = []
}
View-Model scheme in iOS development architecture

Here we’ll create a protocol, just like for the provider. Mention fields such as ViewInputData, and Events. ViewInputData is the data that will be passed directly to our viewController. Let’s create the implementation of our ViewModel, let’s subscribe the viewInputData to the state provider and change it to the necessary format for the view using the mapToViewInputData function. Create an enum Events, where we define all the events that should be processed on the screen, like view loading, button pressing, cell selection, etc. Make Events a PublishSubject type, to be able to subscribe and add new elements, subscribe and handle each event.

protocol NotesListViewModel: AnyObject {
    var viewInputData: Observable<NotesListViewInputData> { get }
    var events: PublishSubject<NotesListViewEvent> { get }
    
    var onNoteSelected: ((Note) -> ())? { get set }
    var onCreateNote: (() -> ())? { get set }
}
 
class NotesListViewModelImpl: NotesListViewModel {
    let disposeBag = DisposeBag()
    
    let viewInputData: Observable<NotesListViewInputData>
    let events = PublishSubject<NotesListViewEvent>()
    
    let notesProvider: NotesListProvider
    
    var onNoteSelected: ((Note) -> ())?
    var onCreateNote: (() -> ())?
    
    init(notesProvider: NotesListProvider) {
        self.notesProvider = notesProvider
        
        self.viewInputData = notesProvider.state.map { $0.mapToNotesListViewInputData() }
        
        events.subscribe(onNext: { [weak self] event in
            switch event {
            case .viewDidAppear, .viewWillDisappear:
                break
            case let .selectedNote(id):
                self?.noteSelected(id: id)
            case .createNote:
                self?.onCreateNote?()
            }
        }).disposed(by: disposeBag)
    }
    
    private func noteSelected(id: Identifier<Note>) {
        if let note = notesProvider.currentState.notes.first(where: { $0.id == id }) {
            onNoteSelected?(note)
        }
    }
}
 
private extension NotesListProviderState {
    func mapToNotesListViewInputData() -> NotesListViewInputData {
        return NotesListViewInputData(notes: self.notes.map { ($0.id, NoteCollectionViewCell.State(text: $0.text)) })
    }
}
View scheme in iOS mobile architecture

In this layer, we configure the screen UI and bindings with the view model. The View layer represents the UIViewController. In viewWillAppear(), we subscribe to ViewInputData and give the data to render, which distributes it to the desired UI elements

  override func viewWillAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        
        let disposeBag = DisposeBag()
        
        viewModel.viewInputData.subscribe(onNext: { [weak self] viewInputData in
            self?.render(data: viewInputData)
        }).disposed(by: disposeBag)
        
        self.disposeBag = disposeBag
    }
    
    private func render(data: NotesListViewInputData) {
        var snapshot = DiffableDataSourceSnapshot<NotesListSection, NotesListSectionItem>()
        snapshot.appendSections([.list])
        snapshot.appendItems(data.notes.map { NotesListSectionItem.note($0.0, $0.1) })
        dataSource.apply(snapshot)
    }

We also add event bindings, either with RxSwift or the basic way through selectors. 

    @objc private func createNoteBtnPressed() {
        viewModel.events.onNext(.createNote)
    }

Now, that all the components of the module are ready, let’s proceed to link objects between themselves. The module is a class subscribed to the DIPart protocol, which primarily serves to maintain the code hierarchy by combining some parts of the system into a single common class, and in the future includes some, but not all, of the components in the list. Let’s implement the obligatory load(container:) method, where we will register our components.

final class NotesListPart: DIPart {
    static func load(container: DIContainer) {
        container.register(SharedStore.notesListScoped)
            .as(SharedStore<[Note], Never>.self, tag: NotesListScope.self)
            .lifetime(.objectGraph)
        
        container.register { NotesListProviderImpl(sharedStore: by(tag: NotesListScope.self, on: $0)) }
            .as(NotesListProvider.self)
            .lifetime(.objectGraph)
        
        container.register(NotesListViewModelImpl.init(notesProvider:)).as(NotesListViewModel.self).lifetime(.objectGraph)
        container.register(NotesListViewController.init(viewModel:)).lifetime(.objectGraph)
        container.register(NotesListDependency.init(viewModel:viewController:)).lifetime(.prototype)
    }
}
 
struct NotesListDependency {
    let viewModel: NotesListViewModel
    let viewController: NotesListViewController
}

We’ll register components with the method container.register(), sendingthere our object, and specifying the protocol by which it will communicate, as well as the lifetime of the object. We do the same with all the other components

Our module is ready, do not forget to add the module to the container in the AppDelegate. Let’s go to the NoteListCoordinator in the list opening function. Let’s take the required dependency through the container.resolve function, be sure to explicitly declare the type of variable. Then we create event handlers onNoteSelected and onCreateNote, and pass the viewController to the router.

 func setNotesListRoot() {
        let notesListDependency: NotesListDependency = container.resolve()
        router.setRootModule(notesListDependency.viewController)
        notesListDependency.viewModel.onNoteSelected = { [weak self] note in
            self?.pushNoteDetailsScreen(note: note)
        }
        notesListDependency.viewModel.onCreateNote = { [weak self] in
            self?.pushCreateNoteScreen(mode: .create)
        }
    }

Other modules and navigation are created following these steps. In conclusion, we can say that the architecture isn’t without flaws. We could mention a couple problems, such as changing one field in viewInputData forces to update the whole UI but not certain elements of it; underdeveloped common flow of work with UITabBarController and UIPageViewController.

Conclusion

With the creation of the iOS app architecture, it became much easier for us to work. It’s not so scary anymore to replace a colleague on vacation and take on a new project. Solutions for this or that implementation can be viewed by colleagues without puzzling over how to implement it so that it would work properly with our architecture. 

During the year, we have already managed to add the shared storage, error handling for coordinators, improved routing logic, and we aren’t gonna stop there.

Categories
Uncategorized

Jesse from Vodeo, ‘Fora Soft is in a perfect spot of good pricing of projects and development.’

Our copywriter Nikita talked to Jesse Janson about his experience of working with Fora Soft.

Jesse hails from the Janson family. Movie nerds might know the family by their brand, Janson Media. After all, they’ve been in the movie renting business since 1989.

Jesse’s app isn’t officially released yet, but he certainly does have something to say about his work with Fora.

Today we have Jesse Jenson with us. Jesse is a CEO of Vodeo, am I correct?

Yes. So my name is Jesse Janson, and I work at Janson Media, which is our TV and film distribution company. I am in charge of acquisitions and business development. And Vodeo is a mobile first video streaming rental service that we worked on with Fora Soft, and that’s a separate company which. Yes, I’m president of that company.

Tell me a little bit about Vodeo. What is it? How did it come to be?

Vodeo is a rental only mobile service, an application that we’re going to launch on iPhones first. The service is different and unique in the sense that the users can rent movies or TV episodes for a limited period of time. And for this, they use a credit-based system within the app, which allows the cost per movie rental or episode rental to be much lower than any other application or video rental service currently available in the market. So, within Vodeo, our users can pre-buy credits within the app that they can then use to rent movies and rent TV episodes. And each credit to the user only costs them about $0.10 right now.

Was Fora Soft your first choice?

I think it was probably our first. Definitely the first company and only company we’ve worked on for Vodeo and for the development of the application. I certainly researched and spoke to other companies as well.

But after speaking with Fora Soft, we decided to work together, and it’s been a good decision.

Tell me why you turned to Fora Soft? Why us?

The previous work that Fora Soft has done was impressive, and that was an important factor in our decision. Also, the communication was excellent. So I understood the scope of the project, the estimated time, work, and costs that it would take to get the project up and developed. And yeah, it’s been really good. The communication mostly. And then also the previous work was a big factor.

Share your before and after working with us, like what it used to be before you worked with Fora Soft and what it is now?

Sure. Well, before the Vodeo app, it was really just an idea on paper and just a concept. So we really needed Fora Soft to help us with an MVP. in the space and get that up and running. And then once we sort of had that tested and we had a first version, we worked with Fora Soft to update it. It’s still private, just for us to look at. And we went through a few versions of that, and that whole process has been excellent. And now we’re pretty much to a point where I believe we are planning to publicly launch this year, so maybe June.

Congratulations on that. So I believe since you haven’t officially launched yet, you don’t have any measurable figures, any amount of crushes or profit or anything like that, right?

No, not yet. Right now it’s very much private, and in beta. We probably have a pool of maybe 20 private users who have been testing the app on their phones – registering, renting movies and making sure everything works properly. And that whole process has been great so far. The feedback has been very good from everyone in our small private circle.

Let’s talk a little bit about difficulties. If there were any difficulties with working with us, please tell me honestly.

Yeah, no, I haven’t encountered any difficulties yet. Working with Fora Soft, every question or product feature that I’ve requested or asked about has been easily addressed by Fora Soft. I never got the impression that Fora Soft couldn’t implement a feature or an update to where we are so far with the app. Even discussing future possibilities of updates and features on our future roadmap down the line. Fora Soft has said all of it is possible, and we haven’t had any difficulties in that regard in terms of the development and adding new features. It’s been excellent.

What’s the situation where you would think like, all right, I really want this feature, but how do I implement it? Can they implement it? And you talk to Fora Soft and Fora Soft was, like, yes, sure. Easy peasy. We can do that.

Yeah, that’s been the case. Sometimes it’s not a quick “Oh, that’s easy”. Sometimes the project manager I’m working with at Fora Soft will have to bring it to the developer team and ask them if it’s a feature that could be done. Then they will come back to me and provide me with an estimate and explain how long it will take to implement the process. So, yeah, it’s been really good. Some features are easy and quick “Oh, yeah, we could do that quickly and easily”. And some are “let me just check with the developers and see if it’s possible”. And that’s always been “Yeah, it’s possible. We’re able to do it”. So it’s been great.

Qualities like determination and communication and professionalism are really important when it comes to project type of work. Can you please rate us on those qualities on a scale of ten and maybe come up with other qualities if you need to.

Yeah, I give a 10/10 across the board on everything. It’s been really great.

I really enjoy personally working with a project manager.

We’ve worked with two project managers so far at Fora Soft. We worked for a while with our first project manager, maybe a year, and she was excellent. And then there was a transition. She left or moved on and we worked with a new project manager, and that transition was super seamless and easy. A new project manager picked up right where she left off.

Working with a project manager and communicating with them has probably been the most valuable to me. Especially because I’m not a developer by any means, and I’m not able to speak developer language, code and whatnot. But working with a project manager helps that a lot. So I can communicate what I would like to see and have done.They know how to communicate with the developers and the designers and then bring that back to me and let me know how it goes.

I’ll make sure to forward this feedback to Vladimir. He’s a cool guy.

Yeah. Excellent.

Okay. And would you recommend Fora Soft to your friends or colleagues?

Yeah, I’d definitely recommend them. I think Fora Soft certainly has the skills and know-how to develop applications like this very well. Also, I felt like the price in working with Fora Soft was very competitive across the market, and that we liked as well. 

There’s a trade-off. Once you go too low in price, usually the quality of the work is reflected and is poor with the cost being so low. Sometimes, when the price is very high, it doesn’t necessarily mean that the quality will be that much and greated. So, I think Fora Soft is in a perfect spot of good pricing of projects and development. At the same time, they offer very high quality in the work that gets done.

Do you have anything else to add?

No, it’s all been great.

I’m in New York, Fora Soft is in Russia, there’s a time difference. Even with that, the communication’s been great. I don’t feel like we’re that far apart. The work’s been excellent.

It might be around a couple of years of working on this application. We’re pretty at the point where we’re ready to put it out in the public and hopefully get good feedback.

Thanks a lot, as a movie geek myself, I really hope everything will work out. Bye!