What Hosting Provider Suits You: AWS vs DigitalOcean vs Hetzner [2022]

CPS logos connected to a server icon with plain lines

Out of dozens cloud hosting providers in Fora Soft we usually consider and build our projects on three: AWS, Digital Ocean, and Hetzner. None of them is universal so in this article we’ll dive deeper into what specific needs and requirements each can cover.

To know the cost of any of the servers listed for your project, use our Server cost calculator. It considers how many streamers and viewers you anticipate, and what quality your streaming service requires.

But first…

What is a cloud service and a cloud service provider (CSP)?

We describe cloud service as any kind of infrastructure that a third-party provider hosts and makes available to users through the internet. Basically cloud services make the process of data delivery from a user’s device to the processing systems easier. 

All it takes for a cloud service to work is software and hardware. Literally like any other IT thing. What makes it different is that a device, connection to the network, and an OS is enough to access cloud services. 

Cloud service providers use their own data centers, physically located processing systems we already mentioned, and host platform services for customer organizations. Now in detail about 3 of them we use.

AWS (Amazon Web Services)

AWS is the world’s most broadly adopted cloud platform with over 200 fully featured services from data centers globally. Basically a market leader. It serves 245 countries, has 27 already existing and 7 servers coming soon, all around the globe, on all continents.

It’s safe to say they offer the broadest set of technical opportunities. Among their clients are fast-growing startups, largest enterprises, and leading government agencies. 

Why does AWS make a great solution

AWS is highly autonomous in its scalability. That means, the system ongoingly monitors changes in the system it hosts, detects the exact moment when it requires scalability and ensures that automatically. 

That results in a smaller chance of system crashing, better performance, and smoother user experience. 

Also what they have to show off is a plethora of IaaS’s (Infrastructure-as-a-Service) and PaaS’s (Platform-as-a-Service). These are ready-made solutions on a pay-as-you go basis. 

Colorful icons in a cloud shape with AWS logo on top
AWS tools

But obviously, such a smart and complex solution costs money. The AWS cost is the highest out of 3 providers we cover in this article. To not waste a pretty penny, it’s better to think twice if your product really needs that kind of scalability opportunities. One of the ways to figure it out is to ask yourself how many users you expect in the first few months. 

In a nutshell, AWS is a great solution if your company is large and you anticipate a lot of users at once, or if you need more complex, yet ready-made solutions. 


DigitalOcean is another American solution with 14 data centers in total in Asia, Europe, and North America. Their positioning states 3 main principles: keeping it simple, at affordable price, yet high-quality. And they live by that, mostly focusing on developers’ needs. 

Why does DigitalOcean make great solution

Their pricing is one of the most affordable among other providers in general. Still, they show brilliant results in terms of performance. Their network speed is 1Gbps and the start-up time is only 55 seconds. 

As for keeping it simple, DigitalOcean products have neat user-friendly interfaces with one-click deployments. 

DigitalOcean interface and functionality

The downside of DigitalOcean is a not-so-big choice of functionalities and instruments. At least, compared to AWS. That means, in case there’s no suitable tool for your project’s specific requirements, you’ll have to develop your own. 

But after all, the DigitalOcean server cost is almost 7 times less. 

Long story short, DigitalOcean is a great pick if it’s your first release or an MVP and you’re looking for a cheap yet reliable cloud.

Hetzner Cloud

Hetzner Cloud is a German provider with data centers in 4 locations: in Bavaria and Saxony lands in German, in Helsinki, and in the state of Virginia, USA. 

Why does Hetzner Cloud make a great solution

Hetzner Cloud has all the essentials required to start an app, yet they keep prices low and affordable.  

So basically Hetzner, in terms of pricing and effectiveness, is a European alternative to DigitalOcean. As a bonus, it’s not that well known yet, so it’s even cheaper. Perfect for first releases and small startups. 

Since Hetzner locates its data centers mostly in Europe, you might want to consider building on it if you know for sure 90% of your users will be browsing from Europe. Otherwise you can end up losing in speed and overall user experience.

Another con is lack of agileness in choosing servers and tools, same as it is with Digital Ocean. 

Final thoughts

To sum it all up, here’re the essentials to consider when picking a CSP out of 3 listed:

  1. How many users do you expect to use your product at once?
  2. How fast do you plan to grow?
  3. Where are your users browsing from?

And now a quick summary on what each CSP:

You might want to build your product on AWS if you have to serve hundreds of thousands of people at once, have a big enterprise, or plan to grow fast.

DigitalOcean is a great pick for you if you’re looking for a low cost, yet reliable media server.

Hetzner Cloud will make it even cheaper, yet more suitable for Europe (but not limited by it). 

These are the main providers we work with, but if you have your own favorites or non of them seems to be a match, we can turn to any other CSP.

To calculate your monthly server costs, use our Server cost calculator. And to know the overall price of a project, contact us at or our Head of Sales Vadim on Skype: vadim_prushchik


What Should Come After The 1st MVP Release For It to Be a Success?

A funny cat picture with the article alternative title

In Fora Soft, when we release a product for the first time, it’s already an MLP (Minimum lovable product). But usually the first milestone in product development is MVP (Minimum viable product) launch. However, it’s not a point to stop. But what to do next? What comes after the MVP release and how do you know the product is ready? The answers are in this article. 

Gather feedback from your users

If you want your MVP to be a perfect product-market fit, it’s a good idea to make sure your clients understand what the product is about and how they should interact with it before promoting the platform. Otherwise there’s a chance you spend a pretty penny on marketing and get no result. 

Sign up for startup websites

Like these:

  1. Product Hunt 
  2. Indiehackers
  3. Betalist 

The scheme here is quite easy yet effective:

Your profit: 

  • feedback from real users = the “Do they need the product?” hypothesis testing 
  • badge on your website = trust level increase
Badge on a website

Test the platform on users

Determine your target audience 

TA is a group of people that are most likely interested in your product. They share some needs and wants or have similar ones. 

For instance, TA for an LMS-system is students and teachers that take and give lessons online. 

Test your product with focus groups

  1. Gather up a group of 10 people
    They shall be as close to how you describe your TA as possible. It’s better if they’re strangers to you — friends and family might not be objective.
  2. Decide what exactly you want to test
    For instance, your goal is to test how intuitively students interact with the platform.
    Describe the scheme: “Download the app, sign up, make basic actions (sign up for a class, attach a file with a completed assignment)”.
    Plan the result: “9 users will register, 5 will sign up for a class, 3 will attach their files”.
  3. Test the platform
    We recommend watching the users as they interact with the product: face-to-face or on a video call. You’ll notice some minor yet important details the user might’ve missed in a written report.
  4. Gather feedback
    Note what users could and couldn’t do or accomplish. What questions and issues have they faced? Were there lags or bugs they encountered? Did they like using the product, why? Would they recommend it to a friend? Would they use it themselves?

Carry out a customer development interview

Customer development interview (Custdev) — a means to get data while in-depth interviewing TA. While carrying out a custdev you discover current top-of-mind needs, preferred means of communication and content types. You’ll need these for the promoting campaign later. 

Tip: If you’re on a low budget and can’t afford a custdev, prepare the questions in advance and ask them while testing the product with the users. And if you’ve already made past-testing amendments, now it’s a good idea to see if the amendments are good and efficient.

Custdev aims at discovering how and where your TA gets information and what particular points for ads placement will be the most effective. 

There’re some Rules:

  1. Gather up a group of 10 people. They shall be as close to how you describe your TA as possible. Again, it’s better if they’re strangers to you — friends and family might be not objective. 
  2. Make a questionnaire. With the same questions for each of the respondents to prove the hypothesis.
  3. Ask open questions: how, what, why. Let the respondents share their experience in detail. Don’t ask them for a particular solution. Just find out what the problem is and why solving it is important for the respondent. 
  4. Ask follow up questions.
  5. Remain in the present. Ask them how they act right now (no woulda-shoulda-coulda and Past tenses). Focus on how they tend to act. And better not ask about the future or wishes. The respondent may subconsciously want to appear better than they are and respond accordingly, but it won’t correlate with how they act in a real setting. 
  6. Record the interview. You’ll get a bigger load of data and will be able to come back to it anytime you need, the data mining will be more effective.

Questions you might want to ask

Process the feedback

Effective feedback processing and eliminating product flaws are two Atlases of successful products. If a user can’t fulfill their needs and wants with a product, or the first-use experience is unpleasant, they won’t waste their time on it anymore. 

How to process feedback correctly:

  1. Discuss the feedback with the team
  2. Brainstorm on possible solutions and improvements together
  3. Make up a to-do list for the next release. Use MoSCoW prioritization method (Must have, Should have, Could Have, Won’t have (this time)) to prioritize: 

1st Priority: features that the product Must have. These are the essentials for the current development stage. If a product doesn’t have them, it won’t be successful. Basically, an MVP already has it. You can only improve them or add new ones if the custdev shows it’s necessary. 

2nd Priority: features that are important for the current stage but aren’t that critical. A product Should have them in the next development sprint. 

3rd Priority: features that could make a product better if there was additional development budget. It’s what the product Could have at its best. 

4th Priority: features that will definitely not be in the product, at least for the next 2 timeboxes. It’s what it Won’t have. 

We recommend focusing on the 1st and 2nd priorities as they suggest most needed improvements for the current period of time. The 3rd and 4th ones are just possible enhancements. When improving the product, you may see it change dramatically so there won’t be any need for the features of the 3rd and 4th priorities. So it’s better not to waste your funds and your analysts team’s time on that. 

Make changes to the product 

Adapt your MVP to be better product-market fit with your clients. Develop and test 1st and 2nd priorities improvements. Besides custom solutions, some common ones are have the same intentions: explain how to use the platform for newbies and keep them engaged. 


Automated platform introduction to a user. The goal is to demonstrate what it is for, how to use it properly, and what the benefit is. It makes the user experience much smoother and leaves a good first impression.

Minor interface adjustments 

Product adaptation is a nice product enhancement strategy. It’s a pretty common thing when a user can’t find the target action button or doesn’t get how to interact with certain functionalities. It’s crucial to keep abreast, keep track of the feedback. 

This is why sometimes it’s a good idea to test how comprehensible and intuitive the interface is to the user if there’s no additional navigation. It’s easy to assess. Let’s say the user scenario suggests that the “Sign up for a class” action takes 3 steps. When testing the product you see that the user takes some workarounds and accomplishes it in 10 steps. Meaning something in the interface wasn’t clear for them. This will help add new useful features and enhance existing UX and UI in the next product version. 

Referral system

It’s a way for the platform to “collaborate” with the user by rewarding the referrer for attracting new users. Nothing motivates better than personal gain. Think of what would be enough of a motivation for one to stick to their friend asking for a favor. 


One of the significant metrics of the app effectiveness is user engagement. How frequently they open the app, how much time they spend in it. Notifications will help with the first. Pique their interest with words and emojis only. 

Custom email newsletters will work for web apps.

Text update applying Tone of Voice

If you use texts at the MVP stage (in pop-ups, welcome-screens, etc.) make them consistent in Tone of Voice. These are the rules by which the brand communicates with the users. 

Does your product target teens? Use more slang to be on the same wavelength. Is your TA mostly businessmen and entrepreneurs? Address the users more respectfully and make the communication concise, maybe even formal. 

Relevant ToV guarantees that users will perceive the information better, since they better understand how the product works. 

Moreover, if your brand speaks the same language as its TA, some sort of emotional connection and bond establishes between them, as if they were friends. That’s a significant advantage over competition products. 

Promote the platform

Pick the promotion channels and plan the works

  1. Think of the concept
  • Pick the promotion channels 

Based on the custdev data make a list of the most potentially effective sites for placing advertising messages.
For example, your clients are movie geeks that want to save money on online cinema subscriptions. From the interview we learnt that they visit platforms with vouchers and promo codes often. This is where we will place our ads.

Set up the channels in the single brand identity. Use the same logo and an informative profile background. Let your designer make an entire design system, select color solutions for visual content. 

  • Make your advertisement hypotheses (objectives) to test. Make them SMART 

For example, Facebook users will download the app 100 times within 2 weeks. 

  • Calculate the overall costs

Use forecasting services for digital advertisements. When planning a campaign in Google ads, Facebook ads, Snapchat ads, TikTok ads, Twitter ads you’ll see how many impressions (how many unique users will see the ad) and clicks you’ll get for your budget.

Agree on promotional posts with authors/admins personally. Consider the fees and taxes. 

2. Plan content creation works: make briefs for copywriters, designers, photographers, etc. 

Create the content

Use the information you gained from custdev to determine the best content type for your TA. Write copies, record and edit the video, make pictures, and adjust them to your specific placement sites.

If the campaign includes any social media marketing activity, make up a content-plan, a list of posts topics.

Launch a test campaign

Commence promoting the product and testing hypotheses. We recommend launching a test campaign for 2 weeks — this must be enough. Keep track of the changes. If you see that one of the hypotheses doesn’t prove itself and you don’t get the anticipated result, amend the budget allocation, channels selection, etc. 

Launch the campaign in the most effective channels

Draw the conclusions to the test campaign. Select the most effective ads placement sites. Plan the budget and the results based on the test campaign data. “With the $ X budget I’ll get Y new users, N demo requests”. Use this to better plan and calculate ROI of the project in the long-term perspective. 

Final words

Answering the question from this article intro — you know better when your product is ready. What we can say for sure is that there’s always room for improvement in the post MVP phase. And once you have your MVP and have done all the necessary adjustments, you’ll know the direction. To give you an idea, check out what we do next with our clients in CommunityHill, Janson Media, and AppyBee cases.


How to Test WebRTC Stream Quality? Use StreamTest Extension [Free]

StreamTest UI with the article title

As someone specializing in video chat and video streaming services, our QAs (quality assurance angels) used to deal with the problem of how to test a live stream quality with a load of tools and apps, all different for each metrics. But now there’s this all-in-one solution — StreamTest

It’s a Google Chrome Extension, a testing tool for WebRTC video chats like Google Meet, iMind, Whereby, and ProVideoMeeting. Not sure you’re on a WebRTC stream? Enable StreamTest and if you’re trying to test a non-WebRTC stream, the extension will respond with an error. 

a stream page with a non WebRTC stream error
Non-WebRTC stream error

It’ll give you a live status on 7 essential parameters with an insight on the connection and stream behavior. 

Quick note for non-tech folks:

If you have no technical background and all of these indicators are just letters, figures, and colors to you or don’t want to analyze them yourself, just screenshot (Windows: Win + Shift + S; MacOS: Command + Shift + 3) the status you got immediately, download the report as a .csv file, and show both to your tech specialist. 

We are already working on making the plugin accessible for you, so you could test your live streams easily, guys. So follow us on LinkedIn to stay turned. 

TestStream report UI
Video conference live status

Frame rate or just FPS. The higher the better. To calculate it the plugin measures the time between two frames and calculates the average for the last X frames.

Video and Audio delay. Delay is never welcome, so the lower the better. Basically it’s a sum for jitterBufferDelay and average round trip time. Totals are relevant for the last 3*X seconds, where X is the time set in setInterval.

Packet loss. The lower the better. It shows if any packets were lost on the way to the server, as a percentage. Calculated as (packetsLost stat / packetsSent stat) * 100%. 

Resolution. The higher the better. It mostly depends on the participant’s camera and the device you’re using to chat. 

Freezes and stalls. The lower this indicator is the better. It shows how much % of your call was wasted due to lags. Consider something as freeze if the comparison (!wasDocumentJustHidden && dt > Math.max(dtTimestampsQueue.getAverage() * 3, dtTimestampsQueue.getAverage() + 0.150)) is true. In simple terms freeze happens if the time of the current frame is greater than the average time of frame existence multiplied by 3. And at the same time is greater than the average time of frame existence for at least 150ms. 

Bitrate. This indicator is responsible for how smooth the picture is, the higher the indicator the smoother the picture.

Video and Audio codec — for a developer to know how the data is compressed.

In the status you’ll also notice that the indicator measures are colored in Red, Yellow, or Green. Here’s what the colors mean:

Table withe report metrics coloring
StreamTest live status colors meaning

Besides the data we described previously, there’re also SDP offer, answer, iceConnectionState, iceGatheringState, and WebRTC Stats info in the downloadable .csv report. It will give the bigger picture of the connection and the stream behavior. 

StreamTest starts collecting data once you enable it. To do it, click your right mouse button on the other participant’s stream and tap “Test stream” in the context menu. It will stop collecting the data once you close the extension / start testing another stream / go to the extension main screen. 

To test live streams, download this WebRTC tester for your Chrome from the official store here. For free. 


How to Implement Delayed Messages with RabbitMQ? Code Examples

Graphic article cover with colorful command windows

Sometimes you need to implement scheduled or repeating actions into your app. For example, sending a push-notification in 10 minutes or clearing a temporary folder every day.

To do this, you can use cron-tasks, that run scripts on your server automatically, or node-schedule package (a task-planning library for Node.js).

But with both these solutions there’s a scaling problem:

  • There’re several servers so it might be unclear on which one to run the task
  • The selected server might crash
  • The node might get deleted for freed up resources

One of possible solutions here is RabbitMQ, a message broker. Check out the overall delayed messages implementation scheme in this example on GitHub. And here’s what it’s like in detail, step by step:

  1. Create 2 exchangers: regular and delayed one
export const HELLO_EXCHANGE = Object.freeze({ 
    name: 'hello',
    type: 'direct',
    options: {
        durable: true,
   queues: {},

export const HELLO_DELAYED_EXCHANGE = Object.freeze({
    name: 'helloDelayed',
    type: 'direct',
    options: {
        durable: true,
   queues: {},

2. In each of the exchangers create queues with the same binding type but different names.


queues: { 
        WORLD: {
            name: '', // subscribe to this queue
            binding: '',
            options: {
                durable: true,


  queues: {
        WORLD: {
            name: '',
            binding: '',
            options: {
                durable: true,
                queueMode: 'lazy', // set the message to remain in the hard memory

For the delayed-exchanger’s queue, set the x-dead-letter-exchange argument with the regular queue’s name. The argument tells the RabbitMQ broker to transfer the message to this exchanger if it’s not processed.

  arguments: {
                    'x-dead-letter-exchange':, // set the queue to transfer the message to once it’s dead

3. Publish the message to the delayed-exchanger’s queue with the expiration period

// services/base-service/src/broker/hello/publisher.ts
export const publishHelloDelayedWorld = createPublisher({
    exchangeName: exchangeNameDelayed,
    queue: WORLD_DELAYED,
    expirationInMs: 30000, //set when the message dies (in 30s) 

Once the delayed message expires, it will go to the regular exchanger’s queue.

Now you only have to set a consumer for the regular exchanger’s queue:

// services/base-service/src/broker/hello/consumer.ts
export const initHelloExchange = () => Promise.all([
            prefetch: 50,
            log: true,
// services/base-service/src/broker/hello/controller.ts
export const consumeHelloWorld: IBrokerHandler = async ({ payload }) => {
    const result = await world({ name: });;
    // await publishHelloDelayedWorld({ name: }); // if you need to process the message again


If you need to run the action periodically, publish the message to the delayed exchanger again at the end of the consumer section. 

    // await publishHelloDelayedWorld({ name: });

NOTE: RabbitMQ operates on FIFO (first in, first out) – it processes commands in the same order they were set. So if you publish a delayed message with 1 day expiration and a message with 1 minute expiration in the same queue, it will process the second message after the first one, and the target action for the second message will happen a minute after the first.

Eventually, this is what you get:

  1. Create the exchangers and queues
// services/base-service/src/broker/const/exchanges.ts
export const HELLO_EXCHANGE = Object.freeze({
    name: 'hello',
    type: 'direct',
    options: {
        durable: true,
     queues: { 
        WORLD: {
            name: '', // subscribe to this queue
            binding: '',
            options: {
            durable: true,
export const HELLO_DELAYED_EXCHANGE = Object.freeze({
    name: 'helloDelayed',
    type: 'direct',
    options: {
        durable: true,
        queueMode: 'lazy', // specify that the hard memory must store this message
    queues: {
        WORLD: {
            name: '',
            binding: '',
            options: {
                durable: true,
                queueMode: 'lazy', // specify that the hard memory must store this message                arguments: {
                    'x-dead-letter-exchange':, // specify the queue to which the message must relocate after its death

2. Add the publisher that will send the message to the delayed queue

// services/base-service/src/broker/hello/publisher.ts
export const publishHelloDelayedWorld = createPublisher({
    exchangeName: exchangeNameDelayed,
    queue: WORLD_DELAYED,
    expirationInMs: 30000, // set when the message dies (in 30s)

3. Add the consumer for the regular exchanger’s queue

// services/base-service/src/broker/hello/consumer.ts
export const initHelloExchange = () => Promise.all([
            prefetch: 50,
            log: true,
// services/base-service/src/broker/hello/controller.ts
export const consumeHelloWorld: IBrokerHandler = async ({ payload }) => {
    const result = await world({ name: });;
    // await publishHelloDelayedWorld({ name: }); // if you need to process the message again

4. Profit!

There’s also a plugin that does this work for you and makes the implementation easier. You only create one exchanger, one queue, one publisher, and one consumer.

When publishing, the plugin will process the delayed message and, once it’s expired, will transfer the message to the right queue. All on its own. 

With this plugin the scheduled messages are processed in the order of the expiration time. That means, if you publish a message with a 1-day delay and then a message with 1-minute delay, the second one will be processed before the first.

// services/base-service/src/broker/const/exchanges.ts
export const HELLO_PLUGIN_DELAYED_EXCHANGE = Object.freeze({
    name: 'helloPluginDelayed',
    type: 'x-delayed-message', // specify the delayed queue
    options: {
        durable: true,
        arguments: {
            'x-delayed-type': 'direct', // set the recipient         },
    queues: {
            name: '', // subscribe to the queue
            binding: '',
            options: {
                durable: true,

Add publisher that sends the messages to the delayed queue: 

export const publishHelloPluginDelayedWorld = createPublisher({
    exchangeName: exchangeNamePluginDelayed,
    delayInMs: 60000,  // specify when the message should die (60s)

Add consumer to the queue:

// services/base-service/src/broker/hello/consumer.ts
export const initHelloExchange = () => Promise.all([
            prefetch: 50,
            log: true,
// services/base-service/src/broker/hello/controller.ts
export const consumeHelloWorld: IBrokerHandler = async ({ payload }) => {
    const result = await world({ name: });;

Aaand — you’re done!

We regularly use RabbitMQ in our projects. For instance, check out its use case in Janson Media internet TV portfolio. It’s a movie renting service, but make it digital. 

Here we used RabbitMQ delayed messages for the app’s 3 essential features: sending emails and SMS-messages to notify users that, for example, their lease period is almost over; sending messages about completed payments to the socket and sending a notification to the user; sending uploaded videos for further processing. 

Hopefully, implementing delayed messages won’t be like falling down the rabbit hole for you anymore (if it ever was) 🙂


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?


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


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


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 {
        } completionHandler: { error in

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
        case .audioMic:
//             handle audio mic

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 {
        } else {
            let streamLabel = UUID().uuidString.replacingOccurrences(of: "-", with: "")
            self.localMediaStream = peerConnectionFactory.mediaStream(withStreamId: "\\(streamLabel)")
            let audioTrack = peerConnectionFactory.audioTrack(withTrackId: "\\(streamLabel)a0")

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

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

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 = "<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() {

    func stop() {

    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)


    // 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 {

            var orientationValue: Int32 = 0
   0 ..< 4, to: &orientationValue)
                bytesNoCopy: mappedFile.memory.advanced(by: 4),
                count: mappedFile.size - 4,
                deallocator: .none
        }, queue: DispatchQueue.main)

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.



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.


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.


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
Live Streaming protocols (RTMP, HLS, WebRTC) comparison

Now in detail.


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. 


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. 


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. 


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.


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:


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
Product development process scheme

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:


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


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


  • 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


  • Customer and team have different product vision
  • Fragmented documentation 


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


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.


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. 


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.


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.